From f2faa957b7b28616c405f2612e482dfce58e4882 Mon Sep 17 00:00:00 2001 From: Ivan Bondarev Date: Mon, 10 Apr 2023 21:04:27 +0200 Subject: [PATCH] Mono.Debugging --- .../Backend/DissassemblyBuffer.cs | 154 + .../MonoDebugging/Backend/EvaluationResult.cs | 55 + .../MonoDebugging/Backend/IBacktrace.cs | 20 + .../Backend/IDebuggerBackendObject.cs | 34 + .../Backend/IDebuggerSessionFrontend.cs | 44 + .../Backend/IObjectValueSource.cs | 43 + .../Backend/IObjectValueUpdateCallback.cs | 37 + .../Backend/IObjectValueUpdater.cs | 37 + .../MonoDebugging/Backend/IRawValue.cs | 39 + .../MonoDebugging/Backend/IRawValueArray.cs | 40 + .../MonoDebugging/Backend/IRawValueString.cs | 35 + .../MonoDebugging/Backend/UpdateCallback.cs | 68 + .../MonoDebugging/Client/Assembly.cs | 133 + .../MonoDebugging/Client/AssemblyEventArgs.cs | 53 + .../MonoDebugging/Client/AssemblyLine.cs | 90 + .../MonoDebugging/Client/Backtrace.cs | 76 + .../MonoDebugging/Client/BreakEvent.cs | 398 ++ .../MonoDebugging/Client/BreakEventArgs.cs | 43 + .../MonoDebugging/Client/BreakEventInfo.cs | 159 + .../MonoDebugging/Client/BreakEventStatus.cs | 57 + .../MonoDebugging/Client/Breakpoint.cs | 209 + .../Client/BreakpointEventArgs.cs | 43 + .../MonoDebugging/Client/BreakpointStore.cs | 687 ++++ .../MonoDebugging/Client/Catchpoint.cs | 126 + .../Client/CatchpointEventArgs.cs | 43 + .../MonoDebugging/Client/CompletionData.cs | 77 + .../MonoDebugging/Client/DebuggerException.cs | 47 + .../MonoDebugging/Client/DebuggerFeatures.cs | 47 + .../Client/DebuggerLoggingService.cs | 76 + .../MonoDebugging/Client/DebuggerSession.cs | 1859 +++++++++ .../Client/DebuggerSessionOptions.cs | 73 + .../MonoDebugging/Client/DebuggerStartInfo.cs | 92 + .../Client/DebuggerStatistics.cs | 201 + .../MonoDebugging/Client/EvaluationOptions.cs | 149 + .../MonoDebugging/Client/ExceptionInfo.cs | 320 ++ .../Client/FunctionBreakpoint.cs | 233 ++ .../Client/IExpressionEvaluator.cs | 36 + .../MonoDebugging/Client/ObjectPath.cs | 101 + .../MonoDebugging/Client/ObjectValue.cs | 882 ++++ .../MonoDebugging/Client/ObjectValueFlags.cs | 73 + .../MonoDebugging/Client/OutputOptions.cs | 20 + .../MonoDebugging/Client/ProcessEventArgs.cs | 43 + .../MonoDebugging/Client/ProcessInfo.cs | 90 + .../MonoDebugging/Client/RawValue.cs | 311 ++ .../Client/RunToCursorBreakpoint.cs | 34 + .../MonoDebugging/Client/SourceLink.cs | 25 + .../MonoDebugging/Client/SourceLocation.cs | 190 + .../MonoDebugging/Client/StackFrame.cs | 475 +++ .../MonoDebugging/Client/TargetEventArgs.cs | 61 + .../MonoDebugging/Client/TargetEventType.cs | 19 + .../MonoDebugging/Client/ThreadEventArgs.cs | 43 + .../MonoDebugging/Client/ThreadInfo.cs | 146 + .../MonoDebugging/Client/UsageCounter.cs | 55 + .../Evaluation/ArrayElementGroup.cs | 382 ++ .../Evaluation/ArrayValueReference.cs | 82 + .../Evaluation/AsyncEvaluationTracker.cs | 163 + .../Evaluation/AsyncOperationManager.cs | 257 ++ .../MonoDebugging/Evaluation/BaseBacktrace.cs | 296 ++ .../Evaluation/BaseTypeViewSource.cs | 89 + .../Evaluation/EnumerableElementGroup.cs | 163 + .../Evaluation/EvaluationContext.cs | 147 + .../Evaluation/ExceptionInfoSource.cs | 244 ++ .../Evaluation/ExpressionEvaluator.cs | 267 ++ .../Evaluation/FilteredMembersSource.cs | 123 + .../Evaluation/ICollectionAdaptor.cs | 42 + .../MonoDebugging/Evaluation/IObjectSource.cs | 35 + .../Evaluation/IStringAdaptor.cs | 34 + .../Evaluation/LambdaBodyOutputVisitor.cs | 260 ++ .../Evaluation/LiteralValueReference.cs | 180 + .../NRefactoryExpressionEvaluator.cs | 237 ++ .../NRefactoryExpressionEvaluatorVisitor.cs | 1220 ++++++ .../NRefactoryExpressionResolverVisitor.cs | 157 + .../Evaluation/NRefactoryExtensions.cs | 213 + .../Evaluation/NamespaceValueReference.cs | 148 + .../Evaluation/NullValueReference.cs | 97 + .../Evaluation/ObjectValueAdaptor.cs | 1577 ++++++++ .../MonoDebugging/Evaluation/RawViewSource.cs | 83 + .../Evaluation/RemoteFrameObject.cs | 78 + .../Evaluation/RemoteRawValue.cs | 241 ++ .../Evaluation/TimeOutException.cs | 38 + .../Evaluation/TimedEvaluator.cs | 219 + .../Evaluation/TypeValueReference.cs | 207 + .../Evaluation/UserVariableReference.cs | 71 + .../Evaluation/ValueReference.cs | 307 ++ .../MonoDebugging/Soft/ArrayAdaptor.cs | 102 + .../MonoDebugging/Soft/FieldReferenceBatch.cs | 66 + .../MonoDebugging/Soft/FieldValueReference.cs | 213 + .../Soft/InstructionBreakpoint.cs | 21 + .../MonoDebugging/Soft/JsonSourceLink.cs | 9 + .../MonoDebugging/Soft/LocalVariableBatch.cs | 83 + .../MonoDebugging/Soft/PortablePdbData.cs | 171 + .../Soft/PropertyValueReference.cs | 182 + .../MonoDebugging/Soft/SoftDebuggerAdaptor.cs | 2706 +++++++++++++ .../Soft/SoftDebuggerBacktrace.cs | 232 ++ .../MonoDebugging/Soft/SoftDebuggerSession.cs | 3551 +++++++++++++++++ .../Soft/SoftDebuggerStartInfo.cs | 215 + .../Soft/SoftEvaluationContext.cs | 226 ++ .../MonoDebugging/Soft/SoftValueReference.cs | 25 + .../MonoDebugging/Soft/SourceLinkMap.cs | 14 + .../MonoDebugging/Soft/StringAdaptor.cs | 75 + .../MonoDebugging/Soft/Subprocess.cs | 223 ++ .../MonoDebugging/Soft/ThisValueReference.cs | 79 + .../Soft/VariableValueReference.cs | 145 + .../VisualPascalABCNETLinux.csproj | 182 + 104 files changed, 24678 insertions(+) create mode 100644 VisualPascalABCNETLinux/MonoDebugging/Backend/DissassemblyBuffer.cs create mode 100644 VisualPascalABCNETLinux/MonoDebugging/Backend/EvaluationResult.cs create mode 100644 VisualPascalABCNETLinux/MonoDebugging/Backend/IBacktrace.cs create mode 100644 VisualPascalABCNETLinux/MonoDebugging/Backend/IDebuggerBackendObject.cs create mode 100644 VisualPascalABCNETLinux/MonoDebugging/Backend/IDebuggerSessionFrontend.cs create mode 100644 VisualPascalABCNETLinux/MonoDebugging/Backend/IObjectValueSource.cs create mode 100644 VisualPascalABCNETLinux/MonoDebugging/Backend/IObjectValueUpdateCallback.cs create mode 100644 VisualPascalABCNETLinux/MonoDebugging/Backend/IObjectValueUpdater.cs create mode 100644 VisualPascalABCNETLinux/MonoDebugging/Backend/IRawValue.cs create mode 100644 VisualPascalABCNETLinux/MonoDebugging/Backend/IRawValueArray.cs create mode 100644 VisualPascalABCNETLinux/MonoDebugging/Backend/IRawValueString.cs create mode 100644 VisualPascalABCNETLinux/MonoDebugging/Backend/UpdateCallback.cs create mode 100644 VisualPascalABCNETLinux/MonoDebugging/Client/Assembly.cs create mode 100644 VisualPascalABCNETLinux/MonoDebugging/Client/AssemblyEventArgs.cs create mode 100644 VisualPascalABCNETLinux/MonoDebugging/Client/AssemblyLine.cs create mode 100644 VisualPascalABCNETLinux/MonoDebugging/Client/Backtrace.cs create mode 100644 VisualPascalABCNETLinux/MonoDebugging/Client/BreakEvent.cs create mode 100644 VisualPascalABCNETLinux/MonoDebugging/Client/BreakEventArgs.cs create mode 100644 VisualPascalABCNETLinux/MonoDebugging/Client/BreakEventInfo.cs create mode 100644 VisualPascalABCNETLinux/MonoDebugging/Client/BreakEventStatus.cs create mode 100644 VisualPascalABCNETLinux/MonoDebugging/Client/Breakpoint.cs create mode 100644 VisualPascalABCNETLinux/MonoDebugging/Client/BreakpointEventArgs.cs create mode 100644 VisualPascalABCNETLinux/MonoDebugging/Client/BreakpointStore.cs create mode 100644 VisualPascalABCNETLinux/MonoDebugging/Client/Catchpoint.cs create mode 100644 VisualPascalABCNETLinux/MonoDebugging/Client/CatchpointEventArgs.cs create mode 100644 VisualPascalABCNETLinux/MonoDebugging/Client/CompletionData.cs create mode 100644 VisualPascalABCNETLinux/MonoDebugging/Client/DebuggerException.cs create mode 100644 VisualPascalABCNETLinux/MonoDebugging/Client/DebuggerFeatures.cs create mode 100644 VisualPascalABCNETLinux/MonoDebugging/Client/DebuggerLoggingService.cs create mode 100644 VisualPascalABCNETLinux/MonoDebugging/Client/DebuggerSession.cs create mode 100644 VisualPascalABCNETLinux/MonoDebugging/Client/DebuggerSessionOptions.cs create mode 100644 VisualPascalABCNETLinux/MonoDebugging/Client/DebuggerStartInfo.cs create mode 100644 VisualPascalABCNETLinux/MonoDebugging/Client/DebuggerStatistics.cs create mode 100644 VisualPascalABCNETLinux/MonoDebugging/Client/EvaluationOptions.cs create mode 100644 VisualPascalABCNETLinux/MonoDebugging/Client/ExceptionInfo.cs create mode 100644 VisualPascalABCNETLinux/MonoDebugging/Client/FunctionBreakpoint.cs create mode 100644 VisualPascalABCNETLinux/MonoDebugging/Client/IExpressionEvaluator.cs create mode 100644 VisualPascalABCNETLinux/MonoDebugging/Client/ObjectPath.cs create mode 100644 VisualPascalABCNETLinux/MonoDebugging/Client/ObjectValue.cs create mode 100644 VisualPascalABCNETLinux/MonoDebugging/Client/ObjectValueFlags.cs create mode 100644 VisualPascalABCNETLinux/MonoDebugging/Client/OutputOptions.cs create mode 100644 VisualPascalABCNETLinux/MonoDebugging/Client/ProcessEventArgs.cs create mode 100644 VisualPascalABCNETLinux/MonoDebugging/Client/ProcessInfo.cs create mode 100644 VisualPascalABCNETLinux/MonoDebugging/Client/RawValue.cs create mode 100644 VisualPascalABCNETLinux/MonoDebugging/Client/RunToCursorBreakpoint.cs create mode 100644 VisualPascalABCNETLinux/MonoDebugging/Client/SourceLink.cs create mode 100644 VisualPascalABCNETLinux/MonoDebugging/Client/SourceLocation.cs create mode 100644 VisualPascalABCNETLinux/MonoDebugging/Client/StackFrame.cs create mode 100644 VisualPascalABCNETLinux/MonoDebugging/Client/TargetEventArgs.cs create mode 100644 VisualPascalABCNETLinux/MonoDebugging/Client/TargetEventType.cs create mode 100644 VisualPascalABCNETLinux/MonoDebugging/Client/ThreadEventArgs.cs create mode 100644 VisualPascalABCNETLinux/MonoDebugging/Client/ThreadInfo.cs create mode 100644 VisualPascalABCNETLinux/MonoDebugging/Client/UsageCounter.cs create mode 100644 VisualPascalABCNETLinux/MonoDebugging/Evaluation/ArrayElementGroup.cs create mode 100644 VisualPascalABCNETLinux/MonoDebugging/Evaluation/ArrayValueReference.cs create mode 100644 VisualPascalABCNETLinux/MonoDebugging/Evaluation/AsyncEvaluationTracker.cs create mode 100644 VisualPascalABCNETLinux/MonoDebugging/Evaluation/AsyncOperationManager.cs create mode 100644 VisualPascalABCNETLinux/MonoDebugging/Evaluation/BaseBacktrace.cs create mode 100644 VisualPascalABCNETLinux/MonoDebugging/Evaluation/BaseTypeViewSource.cs create mode 100644 VisualPascalABCNETLinux/MonoDebugging/Evaluation/EnumerableElementGroup.cs create mode 100644 VisualPascalABCNETLinux/MonoDebugging/Evaluation/EvaluationContext.cs create mode 100644 VisualPascalABCNETLinux/MonoDebugging/Evaluation/ExceptionInfoSource.cs create mode 100644 VisualPascalABCNETLinux/MonoDebugging/Evaluation/ExpressionEvaluator.cs create mode 100644 VisualPascalABCNETLinux/MonoDebugging/Evaluation/FilteredMembersSource.cs create mode 100644 VisualPascalABCNETLinux/MonoDebugging/Evaluation/ICollectionAdaptor.cs create mode 100644 VisualPascalABCNETLinux/MonoDebugging/Evaluation/IObjectSource.cs create mode 100644 VisualPascalABCNETLinux/MonoDebugging/Evaluation/IStringAdaptor.cs create mode 100644 VisualPascalABCNETLinux/MonoDebugging/Evaluation/LambdaBodyOutputVisitor.cs create mode 100644 VisualPascalABCNETLinux/MonoDebugging/Evaluation/LiteralValueReference.cs create mode 100644 VisualPascalABCNETLinux/MonoDebugging/Evaluation/NRefactoryExpressionEvaluator.cs create mode 100644 VisualPascalABCNETLinux/MonoDebugging/Evaluation/NRefactoryExpressionEvaluatorVisitor.cs create mode 100644 VisualPascalABCNETLinux/MonoDebugging/Evaluation/NRefactoryExpressionResolverVisitor.cs create mode 100644 VisualPascalABCNETLinux/MonoDebugging/Evaluation/NRefactoryExtensions.cs create mode 100644 VisualPascalABCNETLinux/MonoDebugging/Evaluation/NamespaceValueReference.cs create mode 100644 VisualPascalABCNETLinux/MonoDebugging/Evaluation/NullValueReference.cs create mode 100644 VisualPascalABCNETLinux/MonoDebugging/Evaluation/ObjectValueAdaptor.cs create mode 100644 VisualPascalABCNETLinux/MonoDebugging/Evaluation/RawViewSource.cs create mode 100644 VisualPascalABCNETLinux/MonoDebugging/Evaluation/RemoteFrameObject.cs create mode 100644 VisualPascalABCNETLinux/MonoDebugging/Evaluation/RemoteRawValue.cs create mode 100644 VisualPascalABCNETLinux/MonoDebugging/Evaluation/TimeOutException.cs create mode 100644 VisualPascalABCNETLinux/MonoDebugging/Evaluation/TimedEvaluator.cs create mode 100644 VisualPascalABCNETLinux/MonoDebugging/Evaluation/TypeValueReference.cs create mode 100644 VisualPascalABCNETLinux/MonoDebugging/Evaluation/UserVariableReference.cs create mode 100644 VisualPascalABCNETLinux/MonoDebugging/Evaluation/ValueReference.cs create mode 100644 VisualPascalABCNETLinux/MonoDebugging/Soft/ArrayAdaptor.cs create mode 100644 VisualPascalABCNETLinux/MonoDebugging/Soft/FieldReferenceBatch.cs create mode 100644 VisualPascalABCNETLinux/MonoDebugging/Soft/FieldValueReference.cs create mode 100644 VisualPascalABCNETLinux/MonoDebugging/Soft/InstructionBreakpoint.cs create mode 100644 VisualPascalABCNETLinux/MonoDebugging/Soft/JsonSourceLink.cs create mode 100644 VisualPascalABCNETLinux/MonoDebugging/Soft/LocalVariableBatch.cs create mode 100644 VisualPascalABCNETLinux/MonoDebugging/Soft/PortablePdbData.cs create mode 100644 VisualPascalABCNETLinux/MonoDebugging/Soft/PropertyValueReference.cs create mode 100644 VisualPascalABCNETLinux/MonoDebugging/Soft/SoftDebuggerAdaptor.cs create mode 100644 VisualPascalABCNETLinux/MonoDebugging/Soft/SoftDebuggerBacktrace.cs create mode 100644 VisualPascalABCNETLinux/MonoDebugging/Soft/SoftDebuggerSession.cs create mode 100644 VisualPascalABCNETLinux/MonoDebugging/Soft/SoftDebuggerStartInfo.cs create mode 100644 VisualPascalABCNETLinux/MonoDebugging/Soft/SoftEvaluationContext.cs create mode 100644 VisualPascalABCNETLinux/MonoDebugging/Soft/SoftValueReference.cs create mode 100644 VisualPascalABCNETLinux/MonoDebugging/Soft/SourceLinkMap.cs create mode 100644 VisualPascalABCNETLinux/MonoDebugging/Soft/StringAdaptor.cs create mode 100644 VisualPascalABCNETLinux/MonoDebugging/Soft/Subprocess.cs create mode 100644 VisualPascalABCNETLinux/MonoDebugging/Soft/ThisValueReference.cs create mode 100644 VisualPascalABCNETLinux/MonoDebugging/Soft/VariableValueReference.cs diff --git a/VisualPascalABCNETLinux/MonoDebugging/Backend/DissassemblyBuffer.cs b/VisualPascalABCNETLinux/MonoDebugging/Backend/DissassemblyBuffer.cs new file mode 100644 index 000000000..13801ec48 --- /dev/null +++ b/VisualPascalABCNETLinux/MonoDebugging/Backend/DissassemblyBuffer.cs @@ -0,0 +1,154 @@ +// DissassemblyBuffer.cs +// +// Author: +// Lluis Sanchez Gual +// +// Copyright (c) 2008 Novell, Inc (http://www.novell.com) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// +// + +using System; +using System.Collections.Generic; +using Mono.Debugging.Client; + +namespace Mono.Debugging.Backend +{ + public abstract class DissassemblyBuffer + { + List lines = new List (); + int baseIndex = 0; + long baseAddress; + + const int AddrPerLine = 4; + const int ExtraDownLines = 5; + const int ExtraUpLines = 20; + + public DissassemblyBuffer (long baseAddress) + { + this.baseAddress = baseAddress; + } + + public AssemblyLine[] GetLines (int firstIndex, int lastIndex) + { + //Console.WriteLine ("pp GET LINES: " + firstIndex + " " + lastIndex + " " + baseIndex); + + if (lastIndex >= 0) + FillDown (lastIndex); + if (firstIndex < 0) + FillUp (firstIndex); + + AssemblyLine[] array = new AssemblyLine [lastIndex - firstIndex + 1]; + lines.CopyTo (baseIndex + firstIndex, array, 0, lastIndex - firstIndex + 1); + return array; + } + + public void FillUp (int targetLine) + { + if (baseIndex + targetLine >= 0) + return; + + // Lines we are missing + int linesReq = -(baseIndex + targetLine); + + //Console.WriteLine ("pp FILLUP: " + linesReq); + + // Last known valid address + long lastAddr = lines.Count > 0 ? lines [0].Address : baseAddress; + + // Addresses we are going to query to get the required lines + long addr = lastAddr - (linesReq + ExtraUpLines) * AddrPerLine; // 4 is just a guess + + int lastCount = 0; + bool limitFound = false; + AssemblyLine[] alines; + do { + alines = GetLines (addr, lastAddr); + if (alines.Length <= lastCount) { + limitFound = true; + break; + } + addr -= (linesReq + ExtraUpLines - alines.Length) * AddrPerLine; + lastCount = alines.Length; + } + while (alines.Length < linesReq + ExtraUpLines); + + int max = limitFound ? alines.Length : alines.Length - ExtraUpLines; + if (max < 0) max = 0; + + // Fill the lines + for (int n=0; n < max; n++) + lines.Insert (n, alines [n + (alines.Length - max)]); + + long firstAddr = lines [0].Address; + for (int n=0; n < (linesReq - max); n++) { + AssemblyLine line = new AssemblyLine (--firstAddr, ""); + lines.Insert (0, line); + max++; + } + baseIndex += max; + } + + public void FillDown (int targetLine) + { + if (baseIndex + targetLine < lines.Count) + return; + + // Lines we are missing + int linesReq = (baseIndex + targetLine) - lines.Count + 1; + + //Console.WriteLine ("pp FILLDOWN: " + linesReq); + + // Last known valid address + long lastAddr = lines.Count > 0 ? lines [lines.Count - 1].Address : baseAddress; + + // Addresses we are going to query to get the required lines + long addr = lastAddr + (linesReq + ExtraDownLines) * AddrPerLine; // 4 is just a guess + + int lastCount = 0; + bool limitFound = false; + AssemblyLine[] alines; + do { + alines = GetLines (lastAddr, addr); + if (alines.Length <= lastCount) { + limitFound = true; + break; + } + addr += (linesReq + ExtraDownLines - alines.Length) * AddrPerLine; + lastCount = alines.Length; + } + while (alines.Length < linesReq + ExtraDownLines); + + int max = limitFound ? alines.Length : alines.Length - ExtraDownLines; + + // Fill the lines + for (int n=0; n < max; n++) + lines.Add (alines [n]); + + lastAddr = lines [lines.Count - 1].Address; + for (int n=0; n < (linesReq - max); n++) { + AssemblyLine line = new AssemblyLine (++lastAddr, ""); + lines.Add (line); + } + } + + public abstract AssemblyLine[] GetLines (long startAddr, long endAddr); + } +} diff --git a/VisualPascalABCNETLinux/MonoDebugging/Backend/EvaluationResult.cs b/VisualPascalABCNETLinux/MonoDebugging/Backend/EvaluationResult.cs new file mode 100644 index 000000000..4ec7746c6 --- /dev/null +++ b/VisualPascalABCNETLinux/MonoDebugging/Backend/EvaluationResult.cs @@ -0,0 +1,55 @@ +// +// EvaluationResult.cs +// +// Author: +// Lluis Sanchez +// +// Copyright (c) 2013 Xamarin Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +using System; +using Mono.Debugging.Client; + +namespace Mono.Debugging.Backend +{ + + [Serializable] + public class EvaluationResult + { + public EvaluationResult (string value) + { + Value = value; + } + + public EvaluationResult (string value, string displayValue) + { + Value = value; + DisplayValue = displayValue; + } + + public string Value { get; private set; } + public string DisplayValue { get; private set; } + + public override string ToString () + { + return Value; + } + } +} diff --git a/VisualPascalABCNETLinux/MonoDebugging/Backend/IBacktrace.cs b/VisualPascalABCNETLinux/MonoDebugging/Backend/IBacktrace.cs new file mode 100644 index 000000000..7ace99947 --- /dev/null +++ b/VisualPascalABCNETLinux/MonoDebugging/Backend/IBacktrace.cs @@ -0,0 +1,20 @@ + +using Mono.Debugging.Client; + +namespace Mono.Debugging.Backend +{ + public interface IBacktrace: IDebuggerBackendObject + { + int FrameCount { get; } + StackFrame[] GetStackFrames (int firstIndex, int lastIndex); + ObjectValue[] GetLocalVariables (int frameIndex, EvaluationOptions options); + ObjectValue[] GetParameters (int frameIndex, EvaluationOptions options); + ObjectValue GetThisReference (int frameIndex, EvaluationOptions options); + ExceptionInfo GetException (int frameIndex, EvaluationOptions options); + ObjectValue[] GetAllLocals (int frameIndex, EvaluationOptions options); + ObjectValue[] GetExpressionValues (int frameIndex, string[] expressions, EvaluationOptions options); + CompletionData GetExpressionCompletionData (int frameIndex, string exp); + AssemblyLine[] Disassemble (int frameIndex, int firstLine, int count); + ValidationResult ValidateExpression (int frameIndex, string expression, EvaluationOptions options); + } +} diff --git a/VisualPascalABCNETLinux/MonoDebugging/Backend/IDebuggerBackendObject.cs b/VisualPascalABCNETLinux/MonoDebugging/Backend/IDebuggerBackendObject.cs new file mode 100644 index 000000000..2fcfc8497 --- /dev/null +++ b/VisualPascalABCNETLinux/MonoDebugging/Backend/IDebuggerBackendObject.cs @@ -0,0 +1,34 @@ +// +// IDebuggerBackendObject.cs +// +// Author: +// Lluis Sanchez +// +// Copyright (c) 2013 Xamarin Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +using System; + +namespace Mono.Debugging.Backend +{ + public interface IDebuggerBackendObject + { + } +} + diff --git a/VisualPascalABCNETLinux/MonoDebugging/Backend/IDebuggerSessionFrontend.cs b/VisualPascalABCNETLinux/MonoDebugging/Backend/IDebuggerSessionFrontend.cs new file mode 100644 index 000000000..b340b3520 --- /dev/null +++ b/VisualPascalABCNETLinux/MonoDebugging/Backend/IDebuggerSessionFrontend.cs @@ -0,0 +1,44 @@ +// IDebuggerSession.cs +// +// Author: +// Lluis Sanchez Gual +// +// Copyright (c) 2008 Novell, Inc (http://www.novell.com) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// +// + +using System; +using Mono.Debugging.Client; + +namespace Mono.Debugging.Backend +{ + public interface IDebuggerSessionFrontend + { + void NotifyTargetEvent (TargetEventArgs args); + void NotifyTargetOutput (bool isStderr, string line); + void NotifyDebuggerOutput (bool isStderr, string line); + void BindSourceFileBreakpoints (string fullFilePath); + void UnbindSourceFileBreakpoints (string fullFilePath); + + // To be called when the process is ready to run. + void NotifyStarted (); + } +} diff --git a/VisualPascalABCNETLinux/MonoDebugging/Backend/IObjectValueSource.cs b/VisualPascalABCNETLinux/MonoDebugging/Backend/IObjectValueSource.cs new file mode 100644 index 000000000..c9018e5f2 --- /dev/null +++ b/VisualPascalABCNETLinux/MonoDebugging/Backend/IObjectValueSource.cs @@ -0,0 +1,43 @@ +// IObjectValueSource.cs +// +// Authors: Lluis Sanchez Gual +// Jeffrey Stedfast +// +// Copyright (c) 2008 Novell, Inc (http://www.novell.com) +// Copyright (c) 2012 Xamarin Inc. (http://www.xamarin.com) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// +// + +using System; +using Mono.Debugging.Client; + +namespace Mono.Debugging.Backend +{ + public interface IObjectValueSource: IDebuggerBackendObject + { + ObjectValue[] GetChildren (ObjectPath path, int index, int count, EvaluationOptions options); + EvaluationResult SetValue (ObjectPath path, string value, EvaluationOptions options); + ObjectValue GetValue (ObjectPath path, EvaluationOptions options); + + object GetRawValue (ObjectPath path, EvaluationOptions options); + void SetRawValue (ObjectPath path, object value, EvaluationOptions options); + } +} diff --git a/VisualPascalABCNETLinux/MonoDebugging/Backend/IObjectValueUpdateCallback.cs b/VisualPascalABCNETLinux/MonoDebugging/Backend/IObjectValueUpdateCallback.cs new file mode 100644 index 000000000..0b480e61b --- /dev/null +++ b/VisualPascalABCNETLinux/MonoDebugging/Backend/IObjectValueUpdateCallback.cs @@ -0,0 +1,37 @@ +// IObjectValueUpdateCallback.cs +// +// Author: +// Lluis Sanchez Gual +// +// Copyright (c) 2008 Novell, Inc (http://www.novell.com) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// +// + +using System; +using Mono.Debugging.Client; + +namespace Mono.Debugging.Backend +{ + internal interface IObjectValueUpdateCallback + { + void UpdateValue (ObjectValue newValue); + } +} diff --git a/VisualPascalABCNETLinux/MonoDebugging/Backend/IObjectValueUpdater.cs b/VisualPascalABCNETLinux/MonoDebugging/Backend/IObjectValueUpdater.cs new file mode 100644 index 000000000..5bc67dd09 --- /dev/null +++ b/VisualPascalABCNETLinux/MonoDebugging/Backend/IObjectValueUpdater.cs @@ -0,0 +1,37 @@ +// IObjectValueUpdater.cs +// +// Author: +// Lluis Sanchez Gual +// +// Copyright (c) 2008 Novell, Inc (http://www.novell.com) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// +// + +using System; +using Mono.Debugging.Client; + +namespace Mono.Debugging.Backend +{ + public interface IObjectValueUpdater: IDebuggerBackendObject + { + void RegisterUpdateCallbacks (UpdateCallback[] callbacks); + } +} diff --git a/VisualPascalABCNETLinux/MonoDebugging/Backend/IRawValue.cs b/VisualPascalABCNETLinux/MonoDebugging/Backend/IRawValue.cs new file mode 100644 index 000000000..d6ef9f804 --- /dev/null +++ b/VisualPascalABCNETLinux/MonoDebugging/Backend/IRawValue.cs @@ -0,0 +1,39 @@ +// +// IRawValue.cs +// +// Author: +// Lluis Sanchez +// +// Copyright (c) 2013 Xamarin Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +using System; +using Mono.Debugging.Client; + +namespace Mono.Debugging.Backend +{ + public interface IRawValue: IDebuggerBackendObject + { + object CallMethod (string name, object[] parameters, EvaluationOptions options); + object CallMethod (string name, object[] parameters, out object[] outArgs, EvaluationOptions options); + object GetMemberValue (string name, EvaluationOptions options); + void SetMemberValue (string name, object value, EvaluationOptions options); + } +} diff --git a/VisualPascalABCNETLinux/MonoDebugging/Backend/IRawValueArray.cs b/VisualPascalABCNETLinux/MonoDebugging/Backend/IRawValueArray.cs new file mode 100644 index 000000000..d963e7409 --- /dev/null +++ b/VisualPascalABCNETLinux/MonoDebugging/Backend/IRawValueArray.cs @@ -0,0 +1,40 @@ +// +// IRawValueArray.cs +// +// Author: +// Lluis Sanchez +// +// Copyright (c) 2013 Xamarin Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +using System; +using Mono.Debugging.Client; + +namespace Mono.Debugging.Backend +{ + public interface IRawValueArray: IDebuggerBackendObject + { + object GetValue (int[] index); + Array GetValues (int[] index, int count); + void SetValue (int[] index, object value); + int[] Dimensions { get; } + Array ToArray (); + } +} diff --git a/VisualPascalABCNETLinux/MonoDebugging/Backend/IRawValueString.cs b/VisualPascalABCNETLinux/MonoDebugging/Backend/IRawValueString.cs new file mode 100644 index 000000000..d8e68a22b --- /dev/null +++ b/VisualPascalABCNETLinux/MonoDebugging/Backend/IRawValueString.cs @@ -0,0 +1,35 @@ +// +// IRawValueString.cs +// +// Author: +// Lluis Sanchez +// +// Copyright (c) 2013 Xamarin Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +namespace Mono.Debugging.Backend +{ + public interface IRawValueString: IDebuggerBackendObject + { + string Substring (int index, int length); + string Value { get; } + int Length { get; } + } +} diff --git a/VisualPascalABCNETLinux/MonoDebugging/Backend/UpdateCallback.cs b/VisualPascalABCNETLinux/MonoDebugging/Backend/UpdateCallback.cs new file mode 100644 index 000000000..bdadddfda --- /dev/null +++ b/VisualPascalABCNETLinux/MonoDebugging/Backend/UpdateCallback.cs @@ -0,0 +1,68 @@ +// UpdateCallback.cs +// +// Author: +// Lluis Sanchez Gual +// +// Copyright (c) 2008 Novell, Inc (http://www.novell.com) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// +// + +using System; +using Mono.Debugging.Client; + +namespace Mono.Debugging.Backend +{ + [Serializable] + public class UpdateCallback + { + ObjectPath path; + IObjectValueUpdateCallback callback; + + internal UpdateCallback () + { + } + + internal UpdateCallback (IObjectValueUpdateCallback callback, ObjectPath path) + { + this.callback = callback; + this.path = path; + } + + public ObjectPath Path { + get { return path; } + } + + internal IObjectValueUpdateCallback Callback { + get { + return callback; + } + } + + public void UpdateValue (ObjectValue newValue) + { + try { + callback.UpdateValue (newValue); + } catch { + // Ignore + } + } + } +} diff --git a/VisualPascalABCNETLinux/MonoDebugging/Client/Assembly.cs b/VisualPascalABCNETLinux/MonoDebugging/Client/Assembly.cs new file mode 100644 index 000000000..0963750d1 --- /dev/null +++ b/VisualPascalABCNETLinux/MonoDebugging/Client/Assembly.cs @@ -0,0 +1,133 @@ +// +// Assembly.cs +// +// Author: +// Jonathan Chang +// +// Copyright (c) 2022 +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +using System; +namespace Mono.Debugging.Client +{ + /// + /// Represents the assembly loaded during the debugging session. + /// + public class Assembly + { + public Assembly (string name, string path, bool optimized, bool userCode, string symbolStatus, string symbolFile, int? order, string version, string timestamp, string address, string process, string appdomain, long? processId, bool hasSymbol = false, bool isDynamic = false) + { + Name = name; + Path = path; + Optimized = optimized; + SymbolStatus = symbolStatus; + SymbolFile = symbolFile; + Order = order.GetValueOrDefault (-1); + TimeStamp = timestamp; + Address = address; + Process = process; + AppDomain = appdomain; + Version = version; + UserCode = userCode; + ProcessId = processId; + IsDynamic = isDynamic; + HasSymbols = hasSymbol; + } + + public Assembly (string path) + { + Path = path; + } + + /// + /// Represents the name of the assembly. + /// + public string Name { get; private set; } + + /// + /// Represents the local path of the assembly is loaded from. + /// + public string Path { get; private set; } + + /// + /// Shows if the assembly has been optimized, true if the assembly is optimized. + /// + public bool Optimized { get; private set; } + + /// + /// Shows if the assembly is considered 'user code' by a debugger that supports 'Just My Code'.True if it's considered. + /// + public bool UserCode { get; private set; } + + /// + /// Represents the Description on if symbols were found for the assembly (ex: 'Symbols Loaded', 'Symbols not found', etc. + /// + public string SymbolStatus { get; private set; } + + /// + /// Represents the Logical full path to the symbol file. The exact definition is implementation defined. + /// + public string SymbolFile { get; private set; } + + /// + /// Represents the order in which the assembly was loaded. + /// + public int Order { get; private set; } = -1; + + /// + /// Represents the version of assembly. + /// + public string Version { get; private set; } + + /// + /// Represents the time when the assembly was built in the units of UNIX timestamp formatted as a 64-bit unsigned decimal number in a string. + /// + public string TimeStamp { get; private set; } + + /// + /// Represents the Address where the assembly was loaded as a 64-bit unsigned decimal number. + /// + public string Address { get; private set; } + + /// + /// Represent the process name and process ID the assembly is loaded. + /// + public string Process { get; private set; } + + /// + /// Represent the name of the AppDomain where the assembly is loaded. + /// + public string AppDomain { get; private set; } + + /// + /// Represent the process ID the assembly is loaded. + /// + public long? ProcessId { get; private set; } = -1; + + /// + /// Indicates if the assembly has symbol file. Mainly use for mono project. + /// + public bool HasSymbols { get; private set; } + + /// + /// Indicate if the assembly is a dynamic. Mainly use for mono project. + /// + public bool IsDynamic { get; private set; } + } +} \ No newline at end of file diff --git a/VisualPascalABCNETLinux/MonoDebugging/Client/AssemblyEventArgs.cs b/VisualPascalABCNETLinux/MonoDebugging/Client/AssemblyEventArgs.cs new file mode 100644 index 000000000..c97f79c93 --- /dev/null +++ b/VisualPascalABCNETLinux/MonoDebugging/Client/AssemblyEventArgs.cs @@ -0,0 +1,53 @@ +// +// AssemblyEventArg.cs +// +// Author: +// Matt Ward +// +// Copyright (c) 2018 Microsoft +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +using System; + +namespace Mono.Debugging.Client +{ + public class AssemblyEventArgs : EventArgs + { + public AssemblyEventArgs (Assembly assembly) + { + Location = assembly.Path; + Assembly = assembly; + } + + public AssemblyEventArgs (string location) + { + Location = location; + Assembly = new Assembly (location); + } + + public string Location { + get; private set; + } + + public Assembly Assembly { + get; private set; + } + } +} diff --git a/VisualPascalABCNETLinux/MonoDebugging/Client/AssemblyLine.cs b/VisualPascalABCNETLinux/MonoDebugging/Client/AssemblyLine.cs new file mode 100644 index 000000000..830dc0b34 --- /dev/null +++ b/VisualPascalABCNETLinux/MonoDebugging/Client/AssemblyLine.cs @@ -0,0 +1,90 @@ +// AssemblyLine.cs +// +// Author: +// Lluis Sanchez Gual +// +// Copyright (c) 2008 Novell, Inc (http://www.novell.com) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// +// + +using System; + +namespace Mono.Debugging.Client +{ + [Serializable] + public class AssemblyLine + { + long address; + string code; + int sourceLine; + string addressSpace; + + public string Code { + get { + return code; + } + } + + public long Address { + get { + return address; + } + } + + public string AddressSpace { + get { + return addressSpace; + } + } + + public int SourceLine { + get { + return sourceLine; + } + } + + public bool IsOutOfRange { + get { return address == -1 && code == null; } + } + + public static readonly AssemblyLine OutOfRange = new AssemblyLine (-1, null, null); + + public AssemblyLine (long address, string code): this (address, "", code, -1) + { + } + + public AssemblyLine (long address, string code, int sourceLine): this (address, "", code, sourceLine) + { + } + + public AssemblyLine (long address, string addressSpace, string code): this (address, addressSpace, code, -1) + { + } + + public AssemblyLine (long address, string addressSpace, string code, int sourceLine) + { + this.address = address; + this.code = code; + this.sourceLine = sourceLine; + this.addressSpace = addressSpace; + } + } +} diff --git a/VisualPascalABCNETLinux/MonoDebugging/Client/Backtrace.cs b/VisualPascalABCNETLinux/MonoDebugging/Client/Backtrace.cs new file mode 100644 index 000000000..b52ddc800 --- /dev/null +++ b/VisualPascalABCNETLinux/MonoDebugging/Client/Backtrace.cs @@ -0,0 +1,76 @@ +using System; +using System.Collections.Generic; +using Mono.Debugging.Backend; + +namespace Mono.Debugging.Client +{ + [Serializable] + public class Backtrace + { + IBacktrace serverBacktrace; + int count; + + [NonSerialized] + DebuggerSession session; + + List frames; + + public Backtrace (IBacktrace serverBacktrace) + { + this.serverBacktrace = serverBacktrace; + + count = serverBacktrace.FrameCount; + + // Get first frame, which is most used(for thread location) + if (count > 0) + GetFrame (0, 1); + } + + internal void Attach (DebuggerSession debuggerSession) + { + session = debuggerSession; + serverBacktrace = session.WrapDebuggerObject (serverBacktrace); + + if (frames != null) { + foreach (var frame in frames) { + frame.Attach (debuggerSession); + frame.SourceBacktrace = serverBacktrace; + } + } + } + + public DebuggerSession DebuggerSession { + get { return session; } + } + + public int FrameCount { + get { return count; } + } + + public StackFrame GetFrame (int n) + { + return GetFrame (n, 20); + } + + StackFrame GetFrame (int index, int fetchMultipleCount) + { + if (frames == null) + frames = new List(); + + if (index >= frames.Count) { + var stackFrames = serverBacktrace.GetStackFrames (frames.Count, index + fetchMultipleCount); + foreach (var frame in stackFrames) { + frame.SourceBacktrace = serverBacktrace; + frame.Index = frames.Count; + frames.Add (frame); + frame.Attach (session); + } + } + + if (frames.Count > 0) + return frames[Math.Min (Math.Max (0, index), frames.Count - 1)]; + + return null; + } + } +} diff --git a/VisualPascalABCNETLinux/MonoDebugging/Client/BreakEvent.cs b/VisualPascalABCNETLinux/MonoDebugging/Client/BreakEvent.cs new file mode 100644 index 000000000..0496cc163 --- /dev/null +++ b/VisualPascalABCNETLinux/MonoDebugging/Client/BreakEvent.cs @@ -0,0 +1,398 @@ +// BreakEvent.cs +// +// Author: +// Lluis Sanchez Gual +// +// Copyright (c) 2008 Novell, Inc (http://www.novell.com) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// +// + +using System; +using System.Xml; + +namespace Mono.Debugging.Client +{ + [Serializable] + public class BreakEvent + { + bool breakIfConditionChanges; + string conditionExpression; + string lastConditionValue; + + [NonSerialized] BreakpointStore store; + [NonSerialized] bool enabled = true; + + HitAction hitAction = HitAction.Break; + string customActionId; + string traceExpression; + int hitCount; + string lastTraceValue; + + public BreakEvent () + { + } + + internal BreakEvent (XmlElement elem, string baseDir) + { + string s = elem.GetAttribute ("enabled"); + if (s.Length > 0) + bool.TryParse (s, out enabled); + s = elem.GetAttribute ("hitAction"); + if (s.Length > 0) + Enum.TryParse (s, out hitAction); + s = elem.GetAttribute ("customActionId"); + if (s.Length > 0) + customActionId = s; + s = elem.GetAttribute ("traceExpression"); + if (s.Length > 0) + traceExpression = s; + s = elem.GetAttribute ("hitCountMode"); + HitCountMode mode; + if (s.Length > 0 && Enum.TryParse (s, out mode)) + HitCountMode = mode; + s = elem.GetAttribute ("hitCount"); + if (s.Length > 0) + int.TryParse (s, out hitCount); + + // this is to facilitate backward compatibility + if (hitCount > 0 && HitCountMode == HitCountMode.None) + HitCountMode = HitCountMode.GreaterThan; + + s = elem.GetAttribute ("conditionExpression"); + if (!string.IsNullOrEmpty (s)) + conditionExpression = s; + + s = elem.GetAttribute ("breakIfConditionChanges"); + if (!string.IsNullOrEmpty (s) && !bool.TryParse (s, out breakIfConditionChanges)) + breakIfConditionChanges = false; + } + + internal virtual XmlElement ToXml (XmlDocument doc, string baseDir) + { + XmlElement elem = doc.CreateElement (GetType().Name); + if (!enabled) + elem.SetAttribute ("enabled", "false"); + if ((hitAction & HitAction.Break) == HitAction.None) + elem.SetAttribute ("hitAction", hitAction.ToString ()); + if (!string.IsNullOrEmpty (customActionId)) + elem.SetAttribute ("customActionId", customActionId); + if (!string.IsNullOrEmpty (traceExpression)) + elem.SetAttribute ("traceExpression", traceExpression); + if (HitCountMode != HitCountMode.None) + elem.SetAttribute ("hitCountMode", HitCountMode.ToString ()); + if (hitCount > 0) + elem.SetAttribute ("hitCount", hitCount.ToString ()); + if (!string.IsNullOrEmpty (conditionExpression)) { + elem.SetAttribute ("conditionExpression", conditionExpression); + if (breakIfConditionChanges) + elem.SetAttribute ("breakIfConditionChanges", "True"); + } + + return elem; + } + + internal static BreakEvent FromXml (XmlElement elem, string baseDir) + { + if (elem.Name == "FunctionBreakpoint") + return new FunctionBreakpoint (elem, baseDir); + if (elem.Name == "Breakpoint") + return new Breakpoint (elem, baseDir); + if (elem.Name == "Catchpoint") + return new Catchpoint (elem, baseDir); + + return null; + } + + /// + /// Gets or sets a value indicating whether this is enabled. + /// + /// + /// true if enabled; otherwise, false. + /// + /// + /// Changes in this property are automatically applied. There is no need to call CommitChanges(). + /// + public bool Enabled { + get { + return enabled; + } + set { + if (store != null && store.IsReadOnly) + return; + bool previous = enabled; + enabled = value; + if (store != null) + store.EnableBreakEvent (this, previous, value); + } + } + + /// + /// Gets the status of the break event + /// + /// + /// The status of the break event for the given debug session + /// + /// + /// Session for which to get the status of the break event + /// + public BreakEventStatus GetStatus (DebuggerSession session) + { + if (store == null || session == null) + return BreakEventStatus.Disconnected; + return session.GetBreakEventStatus (this); + } + + /// + /// Gets a message describing the status of the break event + /// + /// + /// The status message of the break event for the given debug session + /// + /// + /// Session for which to get the status message of the break event + /// + public string GetStatusMessage (DebuggerSession session) + { + if (store == null || session == null) + return string.Empty; + return session.GetBreakEventStatusMessage (this); + } + + /// + /// Gets or sets the expression to be traced when the breakpoint is hit + /// + /// + /// If this break event is hit and the HitAction is TraceExpression, the debugger + /// will evaluate and print the value of this property. + /// The CommitChanges() method has to be called for changes in this + /// property to take effect. + /// + public string TraceExpression { + get { + return traceExpression; + } + set { + traceExpression = value; + } + } + + /// + /// Gets the last value traced. + /// + /// + /// This property returns the last evaluation of TraceExpression. + /// + public string LastTraceValue { + get { + return lastTraceValue; + } + internal set { + lastTraceValue = value; + } + } + + /// + /// Gets or sets the action to be performed when the breakpoint is hit + /// + /// + /// If the value is Break, the debugger will pause the execution. + /// If the value is PrintExpression, the debugger will evaluate and + /// print the value of the TraceExpression property. + /// If the value is CustomAction, the debugger will execute the + /// CustomBreakEventHitHandler callback specified in DebuggerSession, + /// and will provide the value of CustomActionId as argument. + /// The CommitChanges() method has to be called for changes in this + /// property to take effect. + /// + public HitAction HitAction { + get { + return hitAction; + } + set { + hitAction = value; + } + } + + /// + /// Gets or sets the hit count mode. + /// + /// + /// When the break event is hit, the HitCountMode is used to compare the CurrentHitCount + /// with the TargetHitCount to determine if the break event should trigger. + /// + public HitCountMode HitCountMode { + get; set; + } + + /// + /// Gets or sets the target hit count. + /// + /// + /// When the break event is hit, if the value of HitCountMode is not None, then + /// the value of CurrentHitCount will be incremented. Execution will immediately + /// resume if it is determined that the CurrentHitCount vs TargetHitCount + /// comparison does not meet the requirements of HitCountMode. + /// + /// The CommitChanges() method has to be called for changes in this property + /// to take effect. + /// + /// + /// FIXME: rename this to TargetHitCount + public int HitCount { + get { + return hitCount; + } + set { + hitCount = value; + } + } + + /// + /// Gets or sets the current hit count. + /// + /// + /// When the break event is hit, the HitCountMode is used to compare the CurrentHitCount + /// with the TargetHitCount to determine if the break event should trigger. + /// + public int CurrentHitCount { + get; set; + } + + /// + /// Gets or sets the custom action identifier. + /// + /// + /// If this break event is hit and the value of HitAction is CustomAction, + /// the debugger will execute the CustomBreakEventHitHandler callback + /// specified in DebuggerSession, and will provide the value of this property + /// as argument. + /// The CommitChanges() method has to be called for changes in this + /// property to take effect. + /// + public string CustomActionId { + get { + return customActionId; + } + set { + customActionId = value; + } + } + + internal BreakpointStore Store { + get { + return store; + } + set { + store = value; + } + } + + public string ConditionExpression { + get { + return conditionExpression; + } + set { + conditionExpression = value; + } + } + + public string LastConditionValue { + get { + return lastConditionValue; + } + set { + lastConditionValue = value; + } + } + + public bool BreakIfConditionChanges { + get { + return breakIfConditionChanges; + } + set { + breakIfConditionChanges = value; + } + } + + /// + /// NonUserBreakpoint is special kind of breakpoint that is not placed by user but by IDE/Add-In. + /// This breakpoint is usually used by AddIn to execute code on debugee at certain point in application execution. + /// IDE must hide this breakpoint from user. In breakpoints list and it must not stop execution(show breakpoint hit location). + /// WARNING: Code that adds this breakpoint must also make sure to call session.Continue(); when breakpoint is hit. + /// + public bool NonUserBreakpoint { + get; + set; + } + + /// + /// Commits changes done in the break event properties + /// + /// + /// This method must be called after doing changes in the break event properties. + /// + public void CommitChanges () + { + if (store != null) + store.NotifyBreakEventChanged (this); + } + + internal void NotifyUpdate () + { + if (store != null) + store.NotifyBreakEventUpdated (this); + } + + public virtual bool Reset () + { + bool changed = CurrentHitCount != 0; + + CurrentHitCount = 0; + lastConditionValue = null; + + return changed; + } + + /// + /// Clone this instance. + /// + public BreakEvent Clone () + { + return (BreakEvent) MemberwiseClone (); + } + + /// + /// Makes a copy of this instance + /// + /// + /// A break event from which to copy the data. + /// + public virtual void CopyFrom (BreakEvent ev) + { + hitAction = ev.hitAction; + customActionId = ev.customActionId; + traceExpression = ev.traceExpression; + hitCount = ev.hitCount; + breakIfConditionChanges = ev.breakIfConditionChanges; + conditionExpression = ev.conditionExpression; + } + } +} diff --git a/VisualPascalABCNETLinux/MonoDebugging/Client/BreakEventArgs.cs b/VisualPascalABCNETLinux/MonoDebugging/Client/BreakEventArgs.cs new file mode 100644 index 000000000..605a7426c --- /dev/null +++ b/VisualPascalABCNETLinux/MonoDebugging/Client/BreakEventArgs.cs @@ -0,0 +1,43 @@ +// BreakEventArgs.cs +// +// Author: +// Lluis Sanchez Gual +// +// Copyright (c) 2008 Novell, Inc (http://www.novell.com) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// +// + +using System; + +namespace Mono.Debugging.Client +{ + public class BreakEventArgs : EventArgs + { + public BreakEventArgs (BreakEvent be) + { + BreakEvent = be; + } + + public BreakEvent BreakEvent { + get; private set; + } + } +} diff --git a/VisualPascalABCNETLinux/MonoDebugging/Client/BreakEventInfo.cs b/VisualPascalABCNETLinux/MonoDebugging/Client/BreakEventInfo.cs new file mode 100644 index 000000000..8af603c6f --- /dev/null +++ b/VisualPascalABCNETLinux/MonoDebugging/Client/BreakEventInfo.cs @@ -0,0 +1,159 @@ +// +// BreakEventInfo.cs +// +// Author: +// Lluis Sanchez Gual +// +// Copyright (c) 2011 Novell, Inc (http://www.novell.com) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +using System; + +namespace Mono.Debugging.Client +{ + /// + /// This class can be used to manage and get information about a breakpoint + /// at debug-time. It is intended to be used by DebuggerSession subclasses. + /// + public class BreakEventInfo + { + DebuggerSession session; + int adjustedColumn = -1; + int adjustedLine = -1; + + /// + /// Gets or sets the implementation specific handle of the breakpoint + /// + public object Handle { get; set; } + + /// + /// Break event that this instance represents + /// + public BreakEvent BreakEvent { get; internal set; } + + /// + /// Gets the status of the break event + /// + public BreakEventStatus Status { get; private set; } + + public Breakpoint Breakpoint { get; set; } + + /// + /// Gets a description of the status + /// + public string StatusMessage { get; private set; } + + internal void AttachSession (DebuggerSession s, BreakEvent ev) + { + session = s; + BreakEvent = ev; + session.NotifyBreakEventStatusChanged (BreakEvent); + if (adjustedLine != -1 || adjustedColumn != -1) + session.AdjustBreakpointLocation ((Breakpoint)BreakEvent, adjustedLine, adjustedColumn); + } + + /// + /// Adjusts the location of a breakpoint + /// + /// + /// New line. + /// + /// + /// This method can be used to temporarily change source code line of the breakpoint. + /// This is useful, for example, when two adjacent lines are mapped to a single + /// native offset. If the breakpoint is set to the first of those lines, the debugger + /// might end stopping in the second line, because it has the same native offset. + /// To avoid this confusion situation, the debugger implementation may decide to + /// adjust the position of the breakpoint, and move it to the second line. + /// This line adjustment has effect only during the debug session, and is automatically + /// reset when it terminates. + /// + public void AdjustBreakpointLocation (int newLine, int newColumn) + { + if (session != null) { + session.AdjustBreakpointLocation ((Breakpoint)BreakEvent, newLine, newColumn); + } else { + adjustedColumn = newColumn; + adjustedLine = newLine; + } + } + + /// + /// Increments the hit count. + /// + /// true if the break event should trigger, or false otherwise. + public bool HitCountReached + { + get { + switch (BreakEvent.HitCountMode) { + case HitCountMode.LessThan: + return BreakEvent.CurrentHitCount < BreakEvent.HitCount; + case HitCountMode.LessThanOrEqualTo: + return BreakEvent.CurrentHitCount <= BreakEvent.HitCount; + case HitCountMode.EqualTo: + return BreakEvent.CurrentHitCount == BreakEvent.HitCount; + case HitCountMode.GreaterThan: + return BreakEvent.CurrentHitCount > BreakEvent.HitCount; + case HitCountMode.GreaterThanOrEqualTo: + return BreakEvent.CurrentHitCount >= BreakEvent.HitCount; + case HitCountMode.MultipleOf: + return (BreakEvent.CurrentHitCount % BreakEvent.HitCount) == 0; + default: + return true; + } + } + } + + public void IncrementHitCount () + { + if (BreakEvent.HitCountMode != HitCountMode.None) { + BreakEvent.CurrentHitCount++; + BreakEvent.NotifyUpdate (); + } + } + + public void SetStatus (BreakEventStatus s, string statusMessage) + { + if (s != Status) { + Status = s; + StatusMessage = statusMessage; + if (session != null) + session.NotifyBreakEventStatusChanged (BreakEvent); + } + } + + public bool RunCustomBreakpointAction (string actionId) + { + var h = session.CustomBreakEventHitHandler; + return h != null && h (actionId, BreakEvent); + } + + public void UpdateLastTraceValue (string value) + { + BreakEvent.LastTraceValue = value; + BreakEvent.NotifyUpdate (); + if (value != null) { + if (session.BreakpointTraceHandler != null) + session.BreakpointTraceHandler (BreakEvent, value); + else + session.OnTargetDebug (0, "", value + Environment.NewLine); + } + } + } +} diff --git a/VisualPascalABCNETLinux/MonoDebugging/Client/BreakEventStatus.cs b/VisualPascalABCNETLinux/MonoDebugging/Client/BreakEventStatus.cs new file mode 100644 index 000000000..24a94cfde --- /dev/null +++ b/VisualPascalABCNETLinux/MonoDebugging/Client/BreakEventStatus.cs @@ -0,0 +1,57 @@ +// +// BreakEventStatus.cs +// +// Author: +// Lluis Sanchez Gual +// +// Copyright (c) 2011 Novell, Inc (http://www.novell.com) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +using System; + +namespace Mono.Debugging.Client +{ + public enum BreakEventStatus + { + /// + /// The breakpoint is not connected to any debug session + /// + Disconnected = 1, + + /// + /// The breakpoint is not yet bound to a valid location + /// + NotBound = 2, + + /// + /// The breakpoint is bound + /// + Bound = 3, + + /// + /// The breakpoint could not be bound because the breakpoint location is invalid + /// + Invalid = 4, + + /// + /// There was a debugger error while binding the breakpoint + /// + BindError = 5 + } +} diff --git a/VisualPascalABCNETLinux/MonoDebugging/Client/Breakpoint.cs b/VisualPascalABCNETLinux/MonoDebugging/Client/Breakpoint.cs new file mode 100644 index 000000000..f201f2c40 --- /dev/null +++ b/VisualPascalABCNETLinux/MonoDebugging/Client/Breakpoint.cs @@ -0,0 +1,209 @@ +// Breakpoint.cs +// +// Author: +// Lluis Sanchez Gual +// +// Copyright (c) 2008 Novell, Inc (http://www.novell.com) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// +// + +using System; +using System.IO; +using System.Xml; + +namespace Mono.Debugging.Client +{ + [Serializable] + public class Breakpoint: BreakEvent + { + int adjustedColumn = -1; + int adjustedLine = -1; + string fileName; + int column; + int line; + + public Breakpoint (string fileName, int line, int column) + { + FileName = fileName; + Column = column; + Line = line; + } + + public Breakpoint (string fileName, int line) : this (fileName, line, 1) + { + } + + internal Breakpoint (XmlElement elem, string baseDir) : base (elem, baseDir) + { + string s = elem.GetAttribute ("relfile"); + if (!string.IsNullOrEmpty (s) && baseDir != null) { + fileName = Path.Combine (baseDir, s); + } else { + s = elem.GetAttribute ("file"); + if (!string.IsNullOrEmpty (s)) + fileName = s; + } + + s = elem.GetAttribute ("line"); + if (string.IsNullOrEmpty (s) || !int.TryParse (s, out line)) + line = 1; + + s = elem.GetAttribute ("column"); + if (string.IsNullOrEmpty (s) || !int.TryParse (s, out column)) + column = 1; + } + + internal override XmlElement ToXml (XmlDocument doc, string baseDir) + { + XmlElement elem = base.ToXml (doc, baseDir); + + if (!string.IsNullOrEmpty (fileName)) { + elem.SetAttribute ("file", fileName); + if (baseDir != null) { + if (fileName.StartsWith (baseDir, StringComparison.Ordinal)) + elem.SetAttribute ("relfile", fileName.Substring (baseDir.Length).TrimStart (Path.DirectorySeparatorChar)); + } + } + + elem.SetAttribute ("line", line.ToString ()); + elem.SetAttribute ("column", column.ToString ()); + + return elem; + } + + public string FileName { + get { return fileName; } + protected set { fileName = value; } + } + + public int OriginalColumn { + get { return column; } + } + + public int Column { + get { return adjustedColumn == -1 ? column : adjustedColumn; } + protected set { column = value; } + } + + public int OriginalLine { + get { return line; } + } + + public int Line { + get { return adjustedLine == -1 ? line : adjustedLine; } + protected set { line = value; } + } + + public void SetColumn (int newColumn) + { + ResetAdjustedColumn (); + column = newColumn; + } + + public bool UpdatedByEnC { get; set; } + + public void SetLine (int newLine) + { + ResetAdjustedLine (); + line = newLine; + } + + internal void SetFileName (string newFileName) + { + fileName = newFileName; + } + + internal void SetAdjustedColumn (int newColumn) + { + adjustedColumn = newColumn; + } + + internal void SetAdjustedLine (int newLine) + { + adjustedLine = newLine; + } + + // FIXME: make this private + internal void ResetAdjustedColumn () + { + adjustedColumn = -1; + } + + // FIXME: make this private + internal void ResetAdjustedLine () + { + adjustedLine = -1; + } + + public override bool Reset () + { + bool changed = base.Reset () || HasAdjustedLine || HasAdjustedColumn; + + adjustedColumn = -1; + adjustedLine = -1; + + return changed; + } + + // FIXME: make this private + internal bool HasAdjustedColumn { + get { return adjustedColumn != -1; } + } + + // FIXME: make this private + internal bool HasAdjustedLine { + get { return adjustedLine != -1; } + } + + public override void CopyFrom (BreakEvent ev) + { + base.CopyFrom (ev); + + Breakpoint bp = (Breakpoint) ev; + + fileName = bp.fileName; + column = bp.column; + line = bp.line; + } + } + + public enum HitCountMode { + None, + LessThan, + LessThanOrEqualTo, + EqualTo, + GreaterThan, + GreaterThanOrEqualTo, + MultipleOf + } + + [Flags] + public enum HitAction + { + None = 0x0, + Break = 0x1, + PrintExpression = 0x2, + CustomAction = 0x4, + PrintTrace = 0x8 + } + + public delegate bool BreakEventHitHandler (string actionId, BreakEvent be); +} diff --git a/VisualPascalABCNETLinux/MonoDebugging/Client/BreakpointEventArgs.cs b/VisualPascalABCNETLinux/MonoDebugging/Client/BreakpointEventArgs.cs new file mode 100644 index 000000000..51dc4ca64 --- /dev/null +++ b/VisualPascalABCNETLinux/MonoDebugging/Client/BreakpointEventArgs.cs @@ -0,0 +1,43 @@ +// BreakpointEventArgs.cs +// +// Author: +// Lluis Sanchez Gual +// +// Copyright (c) 2008 Novell, Inc (http://www.novell.com) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// +// + +using System; + +namespace Mono.Debugging.Client +{ + public class BreakpointEventArgs : EventArgs + { + public BreakpointEventArgs (Breakpoint bp) + { + Breakpoint = bp; + } + + public Breakpoint Breakpoint { + get; private set; + } + } +} diff --git a/VisualPascalABCNETLinux/MonoDebugging/Client/BreakpointStore.cs b/VisualPascalABCNETLinux/MonoDebugging/Client/BreakpointStore.cs new file mode 100644 index 000000000..220bfdbe2 --- /dev/null +++ b/VisualPascalABCNETLinux/MonoDebugging/Client/BreakpointStore.cs @@ -0,0 +1,687 @@ +// BreakpointStore.cs +// +// Author: +// Lluis Sanchez Gual +// +// Copyright (c) 2008 Novell, Inc (http://www.novell.com) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// +// + +using System; +using System.IO; +using System.Linq; +using System.Xml; +using System.Collections; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Runtime.InteropServices; +using System.Collections.Concurrent; + +namespace Mono.Debugging.Client +{ + public sealed class BreakpointStore: ICollection + { + static readonly StringComparer PathComparer; + static readonly bool IsWindows; + static readonly bool IsMac; + + readonly object breakpointLock = new object (); + List breakpoints = new List(); + + static BreakpointStore () + { + IsWindows = Path.DirectorySeparatorChar == '\\'; + IsMac = !IsWindows && IsRunningOnMac (); + + PathComparer = IsWindows || IsMac ? StringComparer.OrdinalIgnoreCase : StringComparer.Ordinal; + } + + [DllImport ("libc")] + static extern int uname (IntPtr buf); + + //From Managed.Windows.Forms/XplatUI + static bool IsRunningOnMac () + { + var buf = IntPtr.Zero; + + try { + buf = Marshal.AllocHGlobal (8192); + // This is a hacktastic way of getting sysname from uname () + if (uname (buf) == 0) { + var os = Marshal.PtrToStringAnsi (buf); + if (os == "Darwin") + return true; + } + } catch { + } finally { + if (buf != IntPtr.Zero) + Marshal.FreeHGlobal (buf); + } + return false; + } + + public int Count { + get { + return InternalGetBreakpoints().Count; + } + } + + public bool IsReadOnly { + get { + var args = new ReadOnlyCheckEventArgs (); + CheckingReadOnly?.Invoke (this, args); + return args.IsReadOnly; + } + } + + public Breakpoint Add (string filename, int line, int column) + { + return Add (filename, line, column, true); + } + + public Breakpoint Add (string filename, int line) + { + return Add (filename, line, 1, true); + } + + public Breakpoint Add (string filename, int line, int column, bool activate) + { + if (filename == null) + throw new ArgumentNullException (nameof (filename)); + + if (line < 1) + throw new ArgumentOutOfRangeException (nameof (line)); + + if (column < 1) + throw new ArgumentOutOfRangeException (nameof (column)); + + if (IsReadOnly) + return null; + + var bp = new Breakpoint (filename, line, column); + Add (bp); + + return bp; + } + + void ICollection.Add (BreakEvent bp) + { + Add (bp); + } + + public bool Add (BreakEvent be) + { + if (be == null) + throw new ArgumentNullException (nameof (be)); + + if (IsReadOnly) + return false; + + if (be is Breakpoint bp) { + bp.SetFileName (ResolveFullPath(bp.FileName)); + } + + lock (breakpointLock) { + breakpoints.Add(be); + SetBreakpoints(breakpoints); + be.Store = this; + } + + OnBreakEventAdded (be); + + return true; + } + + public Catchpoint AddCatchpoint (string exceptionName) + { + return AddCatchpoint (exceptionName, true); + } + + public Catchpoint AddCatchpoint (string exceptionName, bool includeSubclasses) + { + if (exceptionName == null) + throw new ArgumentNullException (nameof (exceptionName)); + + if (IsReadOnly) + return null; + + var cp = new Catchpoint (exceptionName, includeSubclasses); + Add (cp); + + return cp; + } + + public bool Remove (string filename, int line, int column) + { + if (filename == null) + throw new ArgumentNullException (nameof (filename)); + + if (IsReadOnly) + return false; + + filename = Path.GetFullPath (filename); + + var breakpointsToRemove = new List (); + foreach (var b in InternalGetBreakpoints ()) { + if (b is Breakpoint bp && FileNameEquals(bp.FileName, filename) && + (bp.OriginalLine == line || bp.Line == line) && + (bp.OriginalColumn == column || bp.Column == column)) { + breakpointsToRemove.Add (bp); + } + } + + RemoveRange (breakpointsToRemove); + + return true; + } + + public bool RemoveCatchpoint (string exceptionName) + { + if (exceptionName == null) + throw new ArgumentNullException (nameof (exceptionName)); + + if (IsReadOnly) + return false; + + var breakpointsToRemove = new List (); + foreach (var b in InternalGetBreakpoints ()) { + if (b is Catchpoint cp && cp.ExceptionName == exceptionName) { + breakpointsToRemove.Add (cp); + } + } + + RemoveRange (breakpointsToRemove); + + return true; + } + + public void RemoveRunToCursorBreakpoints () + { + if (IsReadOnly) + return; + + var breakpointsToRemove = InternalGetBreakpoints ().OfType ().ToList (); + + RemoveRange (breakpointsToRemove); + } + + public bool Remove (BreakEvent bp) + { + if (bp == null) + throw new ArgumentNullException (nameof (bp)); + + + if (!IsReadOnly) { + var wasRemoved = false; + lock (breakpointLock) { + if (breakpoints.Contains (bp)) { + breakpoints.Remove(bp); + SetBreakpoints(breakpoints); + wasRemoved = true; + } + } + + if (wasRemoved) { + OnBreakEventRemoved (bp); + return true; + } + } + + return false; + } + + void RemoveRange (IEnumerable breakEvents) + { + List oldEvents; + + if (!IsReadOnly) { + lock (breakpointLock) { + foreach (var b in breakEvents) + breakpoints.Remove(b); + oldEvents = SetBreakpoints(breakpoints); + } + + List breakEventsRemoved = new List (); + + foreach (var bp in breakEvents) { + if (oldEvents.Contains(bp)) { + breakEventsRemoved.Add (bp); + } + } + + OnBreakEventsRemoved (breakEventsRemoved); + } + } + + public Breakpoint Toggle (string filename, int line, int column) + { + if (filename == null) + throw new ArgumentNullException (nameof (filename)); + + if (line < 1) + throw new ArgumentOutOfRangeException (nameof (line)); + + if (column < 1) + throw new ArgumentOutOfRangeException (nameof (column)); + + if (IsReadOnly) + return null; + + var col = GetBreakpointsAtFileLine (filename, line); + if (col.Count > 0) { + // Remove only the most-recently-added breakpoint on the specified line + Remove (col[col.Count - 1]); + return null; + } + + return Add (filename, line, column); + } + + public ReadOnlyCollection GetBreakevents () + { + return new ReadOnlyCollection(InternalGetBreakpoints ()); + } + + public ReadOnlyCollection GetBreakpoints () + { + var list = new List (); + + foreach (var bp in InternalGetBreakpoints ().OfType ()) { + if (!(bp is RunToCursorBreakpoint)) + list.Add (bp); + } + + return list.AsReadOnly (); + } + + public ReadOnlyCollection GetCatchpoints () + { + return InternalGetBreakpoints ().OfType ().ToList ().AsReadOnly (); + } + + public ReadOnlyCollection GetBreakpointsAtFile (string filename) + { + if (filename == null) + throw new ArgumentNullException (nameof (filename)); + + var list = new List (); + if (string.IsNullOrEmpty (filename)) + return list.AsReadOnly (); + + try { + filename = Path.GetFullPath (filename); + } catch { + return list.AsReadOnly (); + } + + foreach (var bp in InternalGetBreakpoints ().OfType ()) { + if (!(bp is RunToCursorBreakpoint) && FileNameEquals(bp.FileName, filename)) + list.Add (bp); + } + + return list.AsReadOnly (); + } + + public ReadOnlyCollection GetBreakpointsAtFileLine (string filename, int line) + { + if (filename == null) + throw new ArgumentNullException (nameof (filename)); + + var list = new List (); + + try { + filename = Path.GetFullPath (filename); + } catch { + return list.AsReadOnly (); + } + + foreach (var bp in InternalGetBreakpoints ().OfType ()) { + if (!(bp is RunToCursorBreakpoint) && FileNameEquals(bp.FileName, filename) && (bp.OriginalLine == line || bp.Line == line)) + list.Add (bp); + } + + return list.AsReadOnly (); + } + + public IEnumerator GetEnumerator () + { + return ((IEnumerable)InternalGetBreakpoints()).GetEnumerator (); + } + + IEnumerator IEnumerable.GetEnumerator () + { + return ((IEnumerable)InternalGetBreakpoints ()).GetEnumerator (); + } + + public void Clear () + { + Clear (true); + } + + public void Clear (bool clearNonUserBreakpoints) + { + var breakpointsToRemove = InternalGetBreakpoints () + .Where (bp => clearNonUserBreakpoints || !bp.NonUserBreakpoint); + + RemoveRange (breakpointsToRemove); + } + + public void ClearBreakpoints () + { + RemoveRange (GetBreakpoints ()); + } + + public void ClearCatchpoints () + { + RemoveRange (GetCatchpoints ()); + } + + public bool Contains (BreakEvent item) + { + return InternalGetBreakpoints ().Contains (item); + } + + public void CopyTo (BreakEvent[] array, int arrayIndex) + { + InternalGetBreakpoints ().CopyTo (array, arrayIndex); + } + + public void UpdateBreakpointLine (Breakpoint bp, int newLine) + { + if (IsReadOnly) + return; + + bp.SetLine (newLine); + NotifyBreakEventChanged (bp); + } + + internal void AdjustBreakpointLine (Breakpoint bp, int newLine, int newColumn) + { + if (IsReadOnly) + return; + + bp.SetAdjustedColumn (newColumn); + bp.SetAdjustedLine (newLine); + NotifyBreakEventChanged (bp); + } + + internal void ResetBreakpoints () + { + if (IsReadOnly) + return; + + foreach (var bp in InternalGetBreakpoints ()) { + if (bp.Reset ()) { + NotifyBreakEventChanged (bp); + } + } + } + + public XmlElement Save (string baseDir = null) + { + XmlDocument doc = new XmlDocument (); + XmlElement elem = doc.CreateElement ("BreakpointStore"); + foreach (BreakEvent ev in this) { + if (ev.NonUserBreakpoint) + continue; + XmlElement be = ev.ToXml (doc, baseDir); + elem.AppendChild (be); + } + return elem; + } + + public void Load (XmlElement rootElem, string baseDir = null) + { + Clear (); + + var loadedBreakpoints = new List (); + foreach (XmlNode n in rootElem.ChildNodes) { + XmlElement elem = n as XmlElement; + if (elem == null) + continue; + BreakEvent ev = BreakEvent.FromXml (elem, baseDir); + if (ev != null) { + loadedBreakpoints.Add (ev); + ev.Store = this; + } + } + + lock (breakpointLock) { + SetBreakpoints (new List (loadedBreakpoints)); + } + + // preserve behaviour by sending an event for each breakpoint that was loaded + OnBreakEventsAdded (loadedBreakpoints); + } + + [DllImport("libc")] + static extern string realpath(string path, IntPtr buffer); + + /// + /// Resolves the full path of the given file + /// + /// + /// The full path if a file is given, or returns the parameter if it is null or empty + static string ResolveFullPath (string path) + { + // If there is no path given, return the same path back + if (string.IsNullOrEmpty(path)) + return path; + + string result = null; + try { + result = realpath(path, IntPtr.Zero); + } + catch { + } + + return string.IsNullOrEmpty(result) ? Path.GetFullPath(path) : result; + } + + public static bool FileNameEquals (string file1, string file2) + { + if (file1 == null) + return file2 == null; + + if (file2 == null) + return false; + + return PathComparer.Compare (file1, file2) == 0; + } + + internal bool EnableBreakEvent (BreakEvent be, bool previouslyEnabled, bool enabled) + { + if (IsReadOnly) + return false; + + if (previouslyEnabled != enabled) { + OnChanged (); + BreakEventEnableStatusChanged?.Invoke (this, new BreakEventArgs (be)); + NotifyStatusChanged (be); + } + + return true; + } + + void OnBreakEventAdded (BreakEvent be) + { + BreakEventAdded?.Invoke (this, new BreakEventArgs (be)); + if (be is Breakpoint bp) { + BreakpointAdded?.Invoke (this, new BreakpointEventArgs (bp)); + } else if (be is Catchpoint ce) { + CatchpointAdded?.Invoke (this, new CatchpointEventArgs (ce)); + } + OnChanged (); + } + + void OnBreakEventsAdded (List bes) + { + foreach (BreakEvent be in bes) { + BreakEventAdded?.Invoke (this, new BreakEventArgs (be)); + if (be is Breakpoint bp) { + BreakpointAdded?.Invoke (this, new BreakpointEventArgs (bp)); + } else if (be is Catchpoint ce) { + CatchpointAdded?.Invoke (this, new CatchpointEventArgs (ce)); + } + } + + OnChanged (); + } + + void OnBreakEventRemoved (BreakEvent be) + { + BreakEventRemoved?.Invoke (this, new BreakEventArgs (be)); + if (be is Breakpoint bp) { + BreakpointRemoved?.Invoke (this, new BreakpointEventArgs (bp)); + } else if (be is Catchpoint ce) { + CatchpointRemoved?.Invoke (this, new CatchpointEventArgs (ce)); + } + OnChanged (); + } + + void OnBreakEventsRemoved (List bes) + { + foreach (BreakEvent be in bes) { + BreakEventRemoved?.Invoke (this, new BreakEventArgs (be)); + if (be is Breakpoint bp) { + BreakpointRemoved?.Invoke (this, new BreakpointEventArgs (bp)); + } else if (be is Catchpoint ce) { + CatchpointRemoved?.Invoke (this, new CatchpointEventArgs (ce)); + } + } + + OnChanged (); + } + + void OnChanged () + { + Changed?.Invoke (this, EventArgs.Empty); + } + + internal void NotifyStatusChanged (BreakEvent be) + { + try { + BreakEventStatusChanged?.Invoke (this, new BreakEventArgs (be)); + if (be is Breakpoint bp) { + BreakpointStatusChanged?.Invoke (this, new BreakpointEventArgs (bp)); + } else if (be is Catchpoint ce) { + CatchpointStatusChanged?.Invoke (this, new CatchpointEventArgs (ce)); + } + } catch { + // Ignore + } + } + + internal void NotifyBreakEventChanged (BreakEvent be) + { + try { + BreakEventModified?.Invoke (this, new BreakEventArgs (be)); + if (be is Breakpoint bp) { + BreakpointModified?.Invoke (this, new BreakpointEventArgs (bp)); + } else if (be is Catchpoint ce) { + CatchpointModified?.Invoke (this, new CatchpointEventArgs (ce)); + } + OnChanged (); + } catch { + // Ignore + } + } + + internal void NotifyBreakEventUpdated (BreakEvent be) + { + + try { + BreakEventUpdated?.Invoke (this, new BreakEventArgs (be)); + if (be is Breakpoint bp) { + BreakpointUpdated?.Invoke (this, new BreakpointEventArgs (bp)); + } else if (be is Catchpoint ce) { + CatchpointUpdated?.Invoke (this, new CatchpointEventArgs (ce)); + } + } catch { + // Ignore + } + } + + public event EventHandler BreakpointAdded; + public event EventHandler BreakpointRemoved; + public event EventHandler BreakpointStatusChanged; + public event EventHandler BreakpointModified; + public event EventHandler BreakpointUpdated; + public event EventHandler CatchpointAdded; + public event EventHandler CatchpointRemoved; + public event EventHandler CatchpointStatusChanged; + public event EventHandler CatchpointModified; + public event EventHandler CatchpointUpdated; + public event EventHandler BreakEventAdded; + public event EventHandler BreakEventRemoved; + public event EventHandler BreakEventStatusChanged; + public event EventHandler BreakEventModified; + public event EventHandler BreakEventUpdated; + public event EventHandler Changed; + public event EventHandler CheckingReadOnly; + + internal event EventHandler BreakEventEnableStatusChanged; + + public void FileRenamed (string oldPath, string newPath) + { + if (IsReadOnly) + return; + + foreach (var bp in GetBreakpointsAtFile (oldPath)) { + Remove (bp); + bp.SetFileName (newPath); + Add (bp); + } + } + + List SetBreakpoints (List newBreakpoints) + { + System.Diagnostics.Debug.Assert (System.Threading.Monitor.IsEntered (breakpointLock), "SetBreakpoints must be called during a lock"); + + foreach (BreakEvent be in newBreakpoints) { + if (be is Breakpoint bp) { + bp.SetFileName (ResolveFullPath(bp.FileName)); + } + } + + var oldEvents = breakpoints; + breakpoints = newBreakpoints; + return oldEvents; + } + + List InternalGetBreakpoints () + { + lock (breakpointLock) { + return breakpoints; + } + } + } + + public class ReadOnlyCheckEventArgs: EventArgs + { + internal bool IsReadOnly; + + public void SetReadOnly (bool isReadOnly) + { + IsReadOnly = IsReadOnly || isReadOnly; + } + } +} diff --git a/VisualPascalABCNETLinux/MonoDebugging/Client/Catchpoint.cs b/VisualPascalABCNETLinux/MonoDebugging/Client/Catchpoint.cs new file mode 100644 index 000000000..a39e0f461 --- /dev/null +++ b/VisualPascalABCNETLinux/MonoDebugging/Client/Catchpoint.cs @@ -0,0 +1,126 @@ +// Catchpoint.cs +// +// Author: +// Lluis Sanchez Gual +// +// Copyright (c) 2008 Novell, Inc (http://www.novell.com) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// +// + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Xml; + +namespace Mono.Debugging.Client +{ + [Serializable] + public class Catchpoint: BreakEvent + { + string exceptionName; + bool includeSubclasses; + + public Catchpoint (string exceptionName) : this (exceptionName, true) + { + } + + public Catchpoint (string exceptionName, bool includeSubclasses) + { + this.exceptionName = exceptionName; + this.includeSubclasses = includeSubclasses; + } + + internal Catchpoint (XmlElement elem, string baseDir): base (elem, baseDir) + { + exceptionName = elem.GetAttribute ("exceptionName"); + + var str = elem.GetAttribute ("includeSubclasses"); + if (string.IsNullOrEmpty (str) || !bool.TryParse (str, out includeSubclasses)) { + // fall back to the old default behavior + includeSubclasses = true; + } + foreach (var child in elem.ChildNodes.OfType()) { + Ignored.Add (child.InnerText); + } + } + + internal override XmlElement ToXml (XmlDocument doc, string baseDir) + { + var elem = base.ToXml (doc, baseDir); + elem.SetAttribute ("exceptionName", exceptionName); + elem.SetAttribute ("includeSubclasses", includeSubclasses.ToString ()); + foreach (var item in Ignored.OrderBy(s => s)) { + var newChild = doc.CreateElement ("Ignore"); + newChild.InnerText = item; + elem.AppendChild (newChild); + } + return elem; + } + + public string ExceptionName { + get { return exceptionName; } + set { exceptionName = value; } + } + + public bool IncludeSubclasses { + get { return includeSubclasses; } + set { includeSubclasses = value; } + } + + /// + /// When an exception first happens, we are given the frame of the exception. + /// However by the time the user chooses "Ignore this location" in the exception dialog, + /// the current frame might be a different frame (the one with source code), + /// and not necessarily the bottom frame of the stack. + /// Make sure we remember the original frame where the exception happened + /// so that when it happens next time, we use the same frame signature to compare + /// against. + /// + public string CurrentLocationSignature { get; set; } + + public HashSet Ignored { get; private set; } = new HashSet (StringComparer.Ordinal); + + public bool ShouldIgnore(string type, string locationSignature) + { + return Ignored.Contains (type) || Ignored.Contains (locationSignature); + } + + public override void CopyFrom (BreakEvent ev) + { + base.CopyFrom (ev); + + var cp = (Catchpoint) ev; + exceptionName = cp.exceptionName; + includeSubclasses = cp.includeSubclasses; + Ignored = cp.Ignored; + } + + public void AddIgnore (string ignore) + { + Ignored.Add (ignore); + } + + public void RemoveIgnore (string ignore) + { + Ignored.Remove (ignore); + } + } +} diff --git a/VisualPascalABCNETLinux/MonoDebugging/Client/CatchpointEventArgs.cs b/VisualPascalABCNETLinux/MonoDebugging/Client/CatchpointEventArgs.cs new file mode 100644 index 000000000..392f5aae3 --- /dev/null +++ b/VisualPascalABCNETLinux/MonoDebugging/Client/CatchpointEventArgs.cs @@ -0,0 +1,43 @@ +// CatchpointEventArgs.cs +// +// Author: +// Lluis Sanchez Gual +// +// Copyright (c) 2008 Novell, Inc (http://www.novell.com) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// +// + +using System; + +namespace Mono.Debugging.Client +{ + public class CatchpointEventArgs : EventArgs + { + public CatchpointEventArgs (Catchpoint cp) + { + Catchpoint = cp; + } + + public Catchpoint Catchpoint { + get; private set; + } + } +} diff --git a/VisualPascalABCNETLinux/MonoDebugging/Client/CompletionData.cs b/VisualPascalABCNETLinux/MonoDebugging/Client/CompletionData.cs new file mode 100644 index 000000000..32ffc2a7d --- /dev/null +++ b/VisualPascalABCNETLinux/MonoDebugging/Client/CompletionData.cs @@ -0,0 +1,77 @@ +// CompletionData.cs +// +// Author: +// Lluis Sanchez Gual +// +// Copyright (c) 2008 Novell, Inc (http://www.novell.com) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// +// + +using System; +using System.Collections.Generic; + +namespace Mono.Debugging.Client +{ + [Serializable] + public class CompletionData + { + int expressionLength; + List items = new List (); + + public int ExpressionLength { + get { + return expressionLength; + } + set { + expressionLength = value; + } + } + + public List Items { + get { return items; } + } + } + + [Serializable] + public class CompletionItem + { + string name; + ObjectValueFlags flags; + + public CompletionItem (string name, ObjectValueFlags flags) + { + this.name = name; + this.flags = flags; + } + + public string Name { + get { + return name; + } + } + + public ObjectValueFlags Flags { + get { + return flags; + } + } + } +} diff --git a/VisualPascalABCNETLinux/MonoDebugging/Client/DebuggerException.cs b/VisualPascalABCNETLinux/MonoDebugging/Client/DebuggerException.cs new file mode 100644 index 000000000..5db7dede2 --- /dev/null +++ b/VisualPascalABCNETLinux/MonoDebugging/Client/DebuggerException.cs @@ -0,0 +1,47 @@ +// +// DebuggerException.cs +// +// Author: +// Lluis Sanchez Gual +// +// Copyright (c) 2009 Novell, Inc (http://www.novell.com) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +using System; +using System.Runtime.Serialization; + +namespace Mono.Debugging.Client +{ + [Serializable] + public class DebuggerException: Exception + { + public DebuggerException (SerializationInfo info, StreamingContext ctx): base (info, ctx) + { + } + + public DebuggerException (string message): base (message) + { + } + + public DebuggerException (string message, Exception inner): base (message, inner) + { + } + } +} diff --git a/VisualPascalABCNETLinux/MonoDebugging/Client/DebuggerFeatures.cs b/VisualPascalABCNETLinux/MonoDebugging/Client/DebuggerFeatures.cs new file mode 100644 index 000000000..20606454b --- /dev/null +++ b/VisualPascalABCNETLinux/MonoDebugging/Client/DebuggerFeatures.cs @@ -0,0 +1,47 @@ +// DebuggerFeatures.cs +// +// Author: +// Lluis Sanchez Gual +// +// Copyright (c) 2008 Novell, Inc (http://www.novell.com) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// +// + +using System; + +namespace Mono.Debugging.Client +{ + [Flags] + public enum DebuggerFeatures: uint + { + None = 0, + ConditionalBreakpoints = 1, + Tracepoints = 1 << 1, + Catchpoints = 1 << 2, + Attaching = 1 << 3, + DebugFile = 1 << 4, + Stepping = 1 << 5, + Pause = 1 << 6, + Breakpoints = 1 << 7, + Disassembly = 1 << 8, + All = 0xffffffff + } +} diff --git a/VisualPascalABCNETLinux/MonoDebugging/Client/DebuggerLoggingService.cs b/VisualPascalABCNETLinux/MonoDebugging/Client/DebuggerLoggingService.cs new file mode 100644 index 000000000..6c95e15a6 --- /dev/null +++ b/VisualPascalABCNETLinux/MonoDebugging/Client/DebuggerLoggingService.cs @@ -0,0 +1,76 @@ +// +// DebuggerLoggingService.cs +// +// Author: +// Michael Hutchinson +// +// Copyright (c) 2010 Novell, Inc. (http://www.novell.com) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +using System; + +namespace Mono.Debugging.Client +{ + /// + /// This is a simple abstraction so that MD can plug in its own logging service to handle the Mono.Debugging.Soft + /// error logging, without Mono.Debugging.Soft depending on MonoDevelop.Core. + /// In the absence of a custom logger, it writes to Console. + /// + public static class DebuggerLoggingService + { + public static ICustomLogger CustomLogger { get; set; } + + public static void LogError (string message, Exception ex) + { + if (CustomLogger != null) + CustomLogger.LogError (message, ex); + else + Console.WriteLine (message + (ex != null? System.Environment.NewLine + ex.ToString () : string.Empty)); + } + + public static void LogMessage (string messageFormat, params object[] args) + { + if (CustomLogger != null) + CustomLogger.LogMessage (messageFormat, args); + else + Console.WriteLine (messageFormat, args); + } + + //this is meant to show a GUI if possible + public static void LogAndShowException (string message, Exception ex) + { + if (CustomLogger != null) + CustomLogger.LogAndShowException (message, ex); + else + LogError (message, ex); + } + } + + public interface ICustomLogger + { + void LogError (string message, System.Exception ex); + void LogAndShowException (string message, System.Exception ex); + void LogMessage (string messageFormat, params object[] args); + /// + /// Gets the new debugger log filename. It may return null which means disable logging. + /// + /// The new debugger log filename or null. + string GetNewDebuggerLogFilename (); + } +} diff --git a/VisualPascalABCNETLinux/MonoDebugging/Client/DebuggerSession.cs b/VisualPascalABCNETLinux/MonoDebugging/Client/DebuggerSession.cs new file mode 100644 index 000000000..cdbf22862 --- /dev/null +++ b/VisualPascalABCNETLinux/MonoDebugging/Client/DebuggerSession.cs @@ -0,0 +1,1859 @@ +// DebuggerSession.cs +// +// Author: +// Ankit Jain +// Lluis Sanchez Gual +// +// Copyright (c) 2008 Novell, Inc (http://www.novell.com) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// +// + +using System; +using System.Threading; +using System.Collections.Generic; + +using Mono.Debugging.Backend; +using Mono.Debugging.Evaluation; +using System.Linq; + +namespace Mono.Debugging.Client +{ + public delegate void TargetEventHandler (object sender, TargetEventArgs args); + public delegate void ProcessEventHandler (int processId); + public delegate void ThreadEventHandler (int threadId); + public delegate bool ExceptionHandler (Exception ex); + public delegate string TypeResolverHandler (string identifier, SourceLocation location); + public delegate void BreakpointTraceHandler (BreakEvent be, string trace); + public delegate IExpressionEvaluator GetExpressionEvaluatorHandler (string extension); + public delegate IConnectionDialog ConnectionDialogCreatorExtended (DebuggerStartInfo dsi); + public delegate IConnectionDialog ConnectionDialogCreator (); + + public abstract class DebuggerSession: IDisposable + { + readonly Dictionary breakpoints = new Dictionary (); + readonly Dictionary resolvedExpressionCache = new Dictionary (); + private readonly List assemblies = new List (); + readonly InternalDebuggerSession frontend; + readonly object slock = new object (); + readonly object breakpointStoreLock = new object (); + BreakpointStore breakpointStore; + DebuggerSessionOptions options; + ProcessInfo[] currentProcesses; + ThreadInfo activeThread; + bool ownedBreakpointStore; + bool adjustingBreakpoints; + DebuggerTimer stepTimer; + bool disposed; + bool attached; + + protected OutputOptions outputOptions; + + /// + /// Reports a debugger event + /// + public event EventHandler TargetEvent; + + /// + /// Raised when the debugger resumes execution after being stopped + /// + public event EventHandler TargetStarted; + + /// + /// Raised when the underlying debugging engine has been initialized and it is ready to start execution. + /// + public event EventHandler TargetReady; + + /// + /// Raised when the debugging session is paused + /// + public event EventHandler TargetStopped; + + /// + /// Raised when the execution is interrupted by an external event + /// + public event EventHandler TargetInterrupted; + + /// + /// Raised when a breakpoint is hit + /// + public event EventHandler TargetHitBreakpoint; + + /// + /// Raised when the execution is interrupted due to receiving a signal + /// + public event EventHandler TargetSignaled; + + /// + /// Raised when the debugged process exits + /// + public event EventHandler TargetExited; + + /// + /// Raised when an exception for which there is a catchpoint is thrown + /// + public event EventHandler TargetExceptionThrown; + + /// + /// Raised when an exception is unhandled + /// + public event EventHandler TargetUnhandledException; + + /// + /// Raised when a thread is started in the debugged process + /// + public event EventHandler TargetThreadStarted; + + /// + /// Raised when a thread is stopped in the debugged process + /// + public event EventHandler TargetThreadStopped; + + /// + /// Raised when the 'busy state' of the debugger changes. + /// The debugger may switch to busy state if it is in the middle + /// of an expression evaluation which can't be aborted. + /// + public event EventHandler BusyStateChanged; + + /// + /// Raised when an assembly is loaded + /// + public event EventHandler AssemblyLoaded; + + /// + /// Raised when a subprocess is started + /// + public event EventHandler SubprocessStarted; + + protected DebuggerSession () + { + UseOperationThread = true; + frontend = new InternalDebuggerSession (this); + EvaluationStats = new DebuggerStatistics (nameof (EvaluationStats)); + StepInStats = new DebuggerStatistics (nameof (StepInStats)); + StepOutStats = new DebuggerStatistics (nameof (StepOutStats)); + StepOverStats = new DebuggerStatistics (nameof (StepOverStats)); + StepInstructionStats = new DebuggerStatistics (nameof (StepInstructionStats)); + NextInstructionStats = new DebuggerStatistics (nameof (NextInstructionStats)); + LocalVariableStats = new DebuggerStatistics (nameof (LocalVariableStats)); + LocalVariableRenderingStats = new DebuggerStatistics (nameof (LocalVariableRenderingStats)); + WatchExpressionStats = new DebuggerStatistics (nameof (WatchExpressionStats)); + StackTraceStats = new DebuggerStatistics (nameof (StackTraceStats)); + TooltipStats = new DebuggerStatistics (nameof (TooltipStats)); + CallStackPadUsageCounter = new UsageCounter (); + ImmediatePadUsageCounter = new UsageCounter (); + ThreadsPadUsageCounter = new UsageCounter (); + outputOptions = new OutputOptions { ExceptionMessage = true, ModuleLoaded = true, ModuleUnoaded = true, ProcessExited = true, SymbolSearch = true, ThreadExited = true }; + } + + /// + /// Releases all resource used by the object. + /// + /// + /// Call when you are finished using the . + /// The method leaves the in an unusable + /// state. After calling , you must release all references to the + /// so the garbage collector can reclaim the memory that the + /// was occupying. + /// + public virtual void Dispose () + { + Dispatch (delegate { + if (!disposed) { + disposed = true; + if (!ownedBreakpointStore) + Breakpoints = null; + } + }); + } + + public void SetOutputOptions(OutputOptions options) + { + outputOptions = options; + } + + /// + /// Gets or sets an exception handler to be invoked when an exception is raised by the debugger engine. + /// + /// + /// Notice that this handler will be used to report exceptions in the debugger, not exceptions raised + /// in the debugged process. + /// + public ExceptionHandler ExceptionHandler { + get; set; + } + + /// + /// Gets or sets the connection dialog creator callback. + /// + public ConnectionDialogCreator ConnectionDialogCreator { get; set; } + + /// + /// Gets or sets the connection dialog creator callback. + /// + public ConnectionDialogCreatorExtended ConnectionDialogCreatorExtended { get; set; } + + /// + /// Gets or sets the breakpoint trace handler. + /// + /// + /// This handler is invoked when the value of a tracepoint has to be printed + /// + public BreakpointTraceHandler BreakpointTraceHandler { get; set; } + + /// + /// Gets or sets the type resolver handler. + /// + /// + /// This handler is invoked when the expression evaluator needs to resolve a type name. + /// + public TypeResolverHandler TypeResolverHandler { get; set; } + + /// + /// Gets or sets the an expression evaluator provider + /// + /// + /// This handler is invoked when the debugger needs to get an evaluator for a specific type of file + /// + public GetExpressionEvaluatorHandler GetExpressionEvaluator { get; set; } + + /// + /// Gets or sets the custom break event hit handler. + /// + /// + /// This handler is invoked when a custom breakpoint is hit to determine if the debug session should + /// continue or stop. + /// + public BreakEventHitHandler CustomBreakEventHitHandler { + get; set; + } + + public DebuggerStatistics EvaluationStats { + get; private set; + } + + public DebuggerStatistics StepInStats { + get; private set; + } + + public DebuggerStatistics StepOutStats { + get; private set; + } + + public DebuggerStatistics StepOverStats { + get; private set; + } + + public DebuggerStatistics StepInstructionStats { + get; private set; + } + + public DebuggerStatistics NextInstructionStats { + get; private set; + } + + public DebuggerStatistics LocalVariableStats { + get; private set; + } + + public DebuggerStatistics LocalVariableRenderingStats { + get; private set; + } + + public DebuggerStatistics WatchExpressionStats { + get; private set; + } + + public DebuggerStatistics StackTraceStats { + get; private set; + } + + public DebuggerStatistics TooltipStats { + get; private set; + } + + public UsageCounter CallStackPadUsageCounter { + get; private set; + } + + public UsageCounter ImmediatePadUsageCounter { + get; private set; + } + + public UsageCounter ThreadsPadUsageCounter { + get; private set; + } + + /// + /// Gets assemblies from the debugger session. + /// + public Assembly[] GetAssemblies () + { + lock (assemblies) { + return assemblies.ToArray (); + } + } + + /// + /// Gets assemblies from the debugger session but filter by the specific process ID . + /// + internal Assembly[] GetAssemblies (long processId) + { + lock (assemblies) { + return assemblies.Where (a => a.ProcessId == processId).ToArray (); + } + } + + /// + /// Gets or sets the breakpoint store for the debugger session. + /// + public BreakpointStore Breakpoints { + get { + lock (breakpointStoreLock) { + if (breakpointStore == null) { + Breakpoints = new BreakpointStore (); + ownedBreakpointStore = true; + } + return breakpointStore; + } + } + set { + lock (breakpointStoreLock) { + if (breakpointStore != null) { + foreach (BreakEvent bp in breakpointStore) { + RemoveBreakEvent (bp); + NotifyBreakEventStatusChanged (bp); + } + + breakpointStore.BreakEventAdded -= OnBreakpointAdded; + breakpointStore.BreakEventRemoved -= OnBreakpointRemoved; + breakpointStore.BreakEventModified -= OnBreakpointModified; + breakpointStore.BreakEventEnableStatusChanged -= OnBreakpointStatusChanged; + breakpointStore.CheckingReadOnly -= BreakpointStoreCheckingReadOnly; + breakpointStore.ResetBreakpoints (); + } + + breakpointStore = value; + ownedBreakpointStore = false; + + if (breakpointStore != null) { + breakpointStore.BreakEventAdded += OnBreakpointAdded; + breakpointStore.BreakEventRemoved += OnBreakpointRemoved; + breakpointStore.BreakEventModified += OnBreakpointModified; + breakpointStore.BreakEventEnableStatusChanged += OnBreakpointStatusChanged; + breakpointStore.CheckingReadOnly += BreakpointStoreCheckingReadOnly; + + if (IsConnected) { + Dispatch (delegate { + if (IsConnected) { + foreach (BreakEvent bp in breakpointStore) + AddBreakEvent (bp); + } + }); + } + } + } + } + } + + readonly Queue actionsQueue = new Queue(); + bool threadExecuting; + + void Dispatch (Action action) + { + if (UseOperationThread) { + lock (actionsQueue) { + actionsQueue.Enqueue (action); + if (!threadExecuting) { + threadExecuting = true; + ThreadPool.QueueUserWorkItem (delegate { + while (true) { + Action actionToExecute = null; + lock (actionsQueue) { + if (actionsQueue.Count > 0) { + actionToExecute = actionsQueue.Dequeue (); + } else { + threadExecuting = false; + return; + } + } + lock (slock) { + try { + actionToExecute (); + } catch (Exception ex) { + HandleException (ex); + } + } + } + }); + } + } + } else { + lock (slock) { + action (); + } + } + } + + /// + /// Starts a debugging session + /// + /// + /// Startup information + /// + /// + /// Session options + /// + /// + /// Is thrown when an argument passed to a method is invalid because it is . + /// + public void Run (DebuggerStartInfo startInfo, DebuggerSessionOptions options) + { + if (startInfo == null) + throw new ArgumentNullException (nameof (startInfo)); + if (options == null) + throw new ArgumentNullException (nameof (options)); + + Interlocked.Exchange (ref this.options, options); + + Dispatch (delegate { + try { + OnRunning (); + OnRun (startInfo); + } catch (Exception ex) { + // should handle exception before raising Exit event because HandleException may ignore exceptions in Exited state + var exceptionHandled = HandleException (ex); + ForceExit (); + if (!exceptionHandled) + throw; + } + }); + } + + /// + /// Starts a debugging session by attaching the debugger to a running process + /// + /// + /// Process information + /// + /// + /// Debugging options + /// + /// + /// Is thrown when an argument passed to a method is invalid because it is . + /// + public void AttachToProcess (ProcessInfo proc, DebuggerSessionOptions options) + { + if (proc == null) + throw new ArgumentNullException (nameof (proc)); + if (options == null) + throw new ArgumentNullException (nameof (options)); + + Interlocked.Exchange (ref this.options, options); + + Dispatch (delegate { + try { + OnRunning (); + OnAttachToProcess (proc); + attached = true; + } catch (Exception ex) { + // should handle exception before raising Exit event because HandleException may ignore exceptions in Exited state + var exceptionHandled = HandleException (ex); + ForceExit (); + if (!exceptionHandled) + throw; + } + }); + } + + /// + /// Detaches this debugging session from the debugged process + /// + public void Detach () + { + Dispatch (delegate { + try { + OnDetach (); + } catch (Exception ex) { + if (!HandleException (ex)) + throw; + } finally { + IsConnected = false; + } + }); + } + + /// + /// Gets a value indicating whether this has been attached to a process using the Attach method. + /// + /// + /// true if attached to process; otherwise, false. + /// + public bool AttachedToProcess { + get { return attached; } + } + + /// + /// Gets or sets the active thread. + /// + /// + /// This property can only be used when the debugger is paused + /// + public ThreadInfo ActiveThread { + get { + return activeThread; + } + set { + lock (slock) { + try { + activeThread = value; + OnSetActiveThread (activeThread.ProcessId, activeThread.Id); + } catch (Exception ex) { + if (!HandleException (ex)) + throw; + } + } + } + } + + void StartStepTimer (DebuggerStatistics stats) + { + stepTimer?.Dispose (); + stepTimer = stats.StartTimer (null); + } + + /// + /// Executes one line of code + /// + public void NextLine () + { + Dispatch (delegate { + OnRunning (); + StartStepTimer (StepOverStats); + try { + OnNextLine (); + } catch (Exception ex) { + ForceStop (); + if (!HandleException (ex)) + throw; + } + }); + } + + /// + /// Executes one line of code, stepping into method invocations + /// + public void StepLine () + { + Dispatch (delegate { + OnRunning (); + StartStepTimer (StepInStats); + try { + OnStepLine (); + } catch (Exception ex) { + ForceStop (); + if (!HandleException (ex)) + throw; + } + }); + } + + /// + /// Executes one low level instruction + /// + public void NextInstruction () + { + Dispatch (delegate { + OnRunning (); + StartStepTimer (NextInstructionStats); + try { + OnNextInstruction (); + } catch (Exception ex) { + ForceStop (); + if (!HandleException (ex)) + throw; + } + }); + } + + /// + /// Executes one low level instruction, stepping into method invocations + /// + public void StepInstruction () + { + Dispatch (delegate { + OnRunning (); + StartStepTimer (StepInstructionStats); + try { + OnStepInstruction (); + } catch (Exception ex) { + ForceStop (); + if (!HandleException (ex)) + throw; + } + }); + } + + /// + /// Resumes the execution of the debugger and stops when the current method is exited + /// + public void Finish () + { + Dispatch (delegate { + OnRunning (); + StartStepTimer (StepOutStats); + try { + OnFinish (); + } catch (Exception ex) { + // should handle exception before raising Exit event because HandleException may ignore exceptions in Exited state + var exceptionHandled = HandleException (ex); + ForceExit (); + if (!exceptionHandled) + throw; + } + }); + } + + /// + /// Sets the next statement on the active thread. + /// + /// File name. + /// Line. + /// Column. + public void SetNextStatement (string fileName, int line, int column) + { + if (fileName == null) + throw new ArgumentNullException (nameof (fileName)); + + if (fileName.Length == 0) + throw new ArgumentException ("Path cannot be empty.", nameof (fileName)); + + if (line < 1) + throw new ArgumentOutOfRangeException (nameof (line)); + + if (column < 1) + throw new ArgumentOutOfRangeException (nameof (column)); + + if (!IsConnected || IsRunning || !CanSetNextStatement) + throw new NotSupportedException (); + + var thread = ActiveThread; + + if (thread == null) + return; + + OnSetNextStatement (thread.Id, fileName, line, column); + } + + /// + /// Sets the next statement on the active thread. + /// + /// The IL offset. + public void SetNextStatement (int ilOffset) + { + if (ilOffset < 0) + throw new ArgumentOutOfRangeException (nameof (ilOffset)); + + if (!IsConnected || IsRunning || !CanSetNextStatement) + throw new NotSupportedException (); + + var thread = ActiveThread; + + if (thread == null) + return; + + OnSetNextStatement (thread.Id, ilOffset); + } + + /// + /// Returns the status of a breakpoint for this debugger session. + /// + public BreakEventStatus GetBreakEventStatus (BreakEvent be) + { + if (IsConnected) { + lock (breakpoints) { + if (breakpoints.TryGetValue (be, out var binfo)) + return binfo.Status; + } + } + return BreakEventStatus.NotBound; + } + + /// + /// Returns a status message of a breakpoint for this debugger session. + /// + public string GetBreakEventStatusMessage (BreakEvent be) + { + if (IsConnected) { + lock (breakpoints) { + if (breakpoints.TryGetValue (be, out var binfo)) { + if (binfo.StatusMessage != null) + return binfo.StatusMessage; + switch (binfo.Status) { + case BreakEventStatus.BindError: return "The breakpoint could not be bound"; + case BreakEventStatus.Bound: return ""; + case BreakEventStatus.Disconnected: return ""; + case BreakEventStatus.Invalid: return "The breakpoint location is invalid. Perhaps the source line does " + + "not contain any statements, or the source does not correspond to the current binary"; + case BreakEventStatus.NotBound: return "The breakpoint could not yet be bound to a valid location"; + } + } + } + } + return "The breakpoint will not currently be hit"; + } + + void AddBreakEvent (BreakEvent be) + { + try { + var eventInfo = OnInsertBreakEvent (be); + if (eventInfo == null) + throw new InvalidOperationException ("OnInsertBreakEvent can't return a null value. If the breakpoint can't be bound or is invalid, a BreakEventInfo with the corresponding status must be returned"); + lock (breakpoints) { + breakpoints [be] = eventInfo; + } + eventInfo.AttachSession (this, be); + } catch (Exception ex) { + string msg; + + if (be is FunctionBreakpoint) + msg = "Could not set breakpoint at location '" + ((FunctionBreakpoint) be).FunctionName + ":" + ((FunctionBreakpoint) be).Line + "'"; + else if (be is Breakpoint) + msg = "Could not set breakpoint at location '" + ((Breakpoint) be).FileName + ":" + ((Breakpoint) be).Line + "'"; + else + msg = "Could not set catchpoint for exception '" + ((Catchpoint) be).ExceptionName + "'"; + + msg += " (" + ex.Message + ")"; + OnDebuggerOutput (false, msg + "\n"); + HandleException (ex); + } + } + + bool RemoveBreakEvent (BreakEvent be) + { + BreakEventInfo binfo = null; + lock (breakpoints) { + if (!breakpoints.TryGetValue (be, out binfo)) + return true; + + breakpoints.Remove (be); + } + + if (binfo != null) { + try { + OnRemoveBreakEvent (binfo); + } catch (Exception ex) { + if (IsConnected && outputOptions.ExceptionMessage) + OnDebuggerOutput (false, ex.Message); + HandleException (ex); + return false; + } + } + + return true; + } + + void UpdateBreakEventStatus (BreakEvent be) + { + BreakEventInfo binfo = null; + lock (breakpoints) { + if (!breakpoints.TryGetValue (be, out binfo)) + return; + } + + try { + OnEnableBreakEvent (binfo, be.Enabled); + } catch (Exception ex) { + if (IsConnected && outputOptions.ExceptionMessage) + OnDebuggerOutput (false, ex.Message); + HandleException (ex); + } + } + + void UpdateBreakEvent (BreakEvent be) + { + BreakEventInfo binfo; + lock (breakpoints) { + if (!breakpoints.TryGetValue (be, out binfo)) + return; + } + + OnUpdateBreakEvent (binfo); + } + + void OnBreakpointAdded (object s, BreakEventArgs args) + { + if (adjustingBreakpoints) + return; + + if (IsConnected) { + Dispatch (delegate { + if (IsConnected) + AddBreakEvent (args.BreakEvent); + }); + } + } + + void OnBreakpointRemoved (object s, BreakEventArgs args) + { + if (adjustingBreakpoints) + return; + + if (IsConnected) { + Dispatch (delegate { + if (IsConnected) + RemoveBreakEvent (args.BreakEvent); + }); + } + } + + void OnBreakpointModified (object s, BreakEventArgs args) + { + if (IsConnected) { + Dispatch (delegate { + if (IsConnected) + UpdateBreakEvent (args.BreakEvent); + }); + } + } + + void OnBreakpointStatusChanged (object s, BreakEventArgs args) + { + if (IsConnected) { + Dispatch (delegate { + if (IsConnected) + UpdateBreakEventStatus (args.BreakEvent); + }); + } + } + + void BreakpointStoreCheckingReadOnly (object sender, ReadOnlyCheckEventArgs e) + { + e.SetReadOnly (!AllowBreakEventChanges); + } + + /// + /// Gets the debugger options object + /// + public DebuggerSessionOptions Options { + get { return options; } + } + + /// + /// Gets or sets the evaluation options. + /// + public EvaluationOptions EvaluationOptions { + get { return options.EvaluationOptions; } + set { options.EvaluationOptions = value; } + } + + /// + /// Resumes the execution of the debugger + /// + public void Continue () + { + Dispatch (delegate { + OnRunning (); + try { + OnContinue (); + } catch (Exception ex) { + ForceStop (); + if (!HandleException (ex)) + throw; + } + }); + } + + /// + /// Pauses the execution of the debugger + /// + public void Stop () + { + Dispatch (delegate { + try { + OnStop (); + } catch (Exception ex) { + if (!HandleException (ex)) + throw; + } + }); + } + + /// + /// Stops the execution of the debugger by killing the debugged process + /// + public void Exit () + { + Dispatch (delegate { + try { + OnExit (); + } catch (Exception ex) { + if (!HandleException (ex)) + throw; + } + }); + } + + /// + /// Gets a value indicating whether the debuggee is currently connected + /// + public bool IsConnected { + get; private set; + } + + /// + /// Gets a value indicating whether the debuggee is currently running (not paused by the debugger) + /// + public bool IsRunning { + get; private set; + } + + /// + /// Gets a value indicating whether the debuggee has exited. + /// + public bool HasExited { + get; protected set; + } + + /// + /// Gets a list of all processes + /// + /// + /// This method can only be used when the debuggee is stopped by the debugger + /// + public ProcessInfo[] GetProcesses () + { + lock (slock) { + if (currentProcesses == null) { + currentProcesses = OnGetProcesses (); + foreach (var process in currentProcesses) + process.Attach (this); + } + return currentProcesses; + } + } + + /// + /// Gets or sets the output writer callback. + /// + /// + /// This callback is invoked to print debuggee output + /// + public OutputWriterDelegate OutputWriter { + get; set; + } + + /// + /// Gets or sets the log writer. + /// + /// + /// This callback is invoked to print debugger log messages + /// + public OutputWriterDelegate LogWriter { + get; set; + } + + /// + /// Gets or sets the debug writer. + /// + /// + /// This callback is invoked to print debugge messages + /// called via System.Diagnostics.Debugger.Log + /// + public DebugWriterDelegate DebugWriter { + get; set; + } + + /// + /// Gets the disassembly of a source code file + /// + /// + /// An array of AssemblyLine, with one element for each source code line that could be disassembled + /// + /// + /// The file. + /// + /// + /// This method can only be used when the debuggee is stopped by the debugger + /// + public AssemblyLine[] DisassembleFile (string file) + { + lock (slock) { + return OnDisassembleFile (file); + } + } + + public string ResolveExpression (string expression, string file, int line, int column, int endLine, int endColumn) + { + return ResolveExpression (expression, new SourceLocation (null, file, line, column, endLine, endColumn, null, null)); + } + + public virtual string ResolveExpression (string expression, SourceLocation location) + { + var key = expression + " " + location; + + if (!resolvedExpressionCache.TryGetValue (key, out var resolved)) { + try { + resolved = OnResolveExpression (expression, location); + } catch (Exception ex) { + if (outputOptions.ExceptionMessage) + OnDebuggerOutput (true, "Error while resolving expression: " + ex.Message); + } + resolvedExpressionCache [key] = resolved; + } + + return resolved ?? expression; + } + + /// + /// Stops the execution of background evaluations being done by the debugger + /// + /// + /// This method can only be used when the debuggee is stopped by the debugger + /// + public void CancelAsyncEvaluations () + { + if (UseOperationThread) { + ThreadPool.QueueUserWorkItem (delegate { + OnCancelAsyncEvaluations (); + }); + } else + OnCancelAsyncEvaluations (); + } + + /// + /// Gets a value indicating whether there are background evaluations being done by the debugger + /// which can be cancelled. + /// + /// + /// This method can only be used when the debuggee is stopped by the debugger + /// + public virtual bool CanCancelAsyncEvaluations { + get { return false; } + } + + /// + /// Override to stop the execution of background evaluations being done by the debugger + /// + protected virtual void OnCancelAsyncEvaluations () + { + } + + readonly Dictionary evaluators = new Dictionary (); + readonly ExpressionEvaluator defaultResolver; + + internal IExpressionEvaluator FindExpressionEvaluator (StackFrame frame) + { + if (GetExpressionEvaluator == null) + return null; + + var fileName = frame.SourceLocation?.FileName; + if (string.IsNullOrEmpty (fileName)) + return null; + + var extension = System.IO.Path.GetExtension (fileName); + if (evaluators.TryGetValue (extension, out var result)) + return result; + + result = GetExpressionEvaluator (extension); + evaluators[extension] = result; + + return result; + } + + public ExpressionEvaluator GetEvaluator (StackFrame frame) + { + var result = FindExpressionEvaluator (frame); + if (result == null) + return defaultResolver; + return result.Evaluator; + } + + protected void RaiseStopEvent () + { + TargetEvent?.Invoke (this, new TargetEventArgs (TargetEventType.TargetStopped)); + } + + /// + /// Called when an expression needs to be resolved + /// + /// + /// The expression + /// + /// + /// The source code location + /// + /// + /// The resolved expression + /// + protected virtual string OnResolveExpression (string expression, SourceLocation location) + { + var resolver = defaultResolver; + if (GetExpressionEvaluator != null) + resolver = GetExpressionEvaluator(System.IO.Path.GetExtension(location.FileName))?.Evaluator ?? defaultResolver; + + return resolver.Resolve(this, location, expression); + } + + internal protected string ResolveIdentifierAsType (string identifier, SourceLocation location) + { + if (TypeResolverHandler != null) + return TypeResolverHandler (identifier, location); + + return null; + } + + internal ThreadInfo[] GetThreads (long processId) + { + lock (slock) { + var threads = OnGetThreads (processId); + foreach (var thread in threads) + thread.Attach (this); + return threads; + } + } + + internal protected Backtrace GetBacktrace (long processId, long threadId) + { + lock (slock) { + var bt = OnGetThreadBacktrace (processId, threadId); + if (bt != null) + bt.Attach (this); + return bt; + } + } + + internal long GetElapsedTime (long processId, long threadId) + { + lock (slock) { + return OnGetElapsedTime (processId, threadId); + } + } + + void ForceStop () + { + var args = new TargetEventArgs (TargetEventType.TargetStopped); + OnTargetEvent (args); + } + + void ForceExit () + { + var args = new TargetEventArgs (TargetEventType.TargetExited); + OnTargetEvent (args); + } + + internal protected void OnTargetEvent (TargetEventArgs args) + { + currentProcesses = null; + + if (args.Process != null) + args.Process.Attach (this); + if (args.Thread != null) + args.Thread.Attach (this); + if (args.Backtrace != null) + args.Backtrace.Attach (this); + + EventHandler evnt = null; + switch (args.Type) { + case TargetEventType.ExceptionThrown: + lock (slock) { + IsRunning = false; + args.IsStopEvent = true; + } + evnt = TargetExceptionThrown; + break; + case TargetEventType.TargetExited: + lock (slock) { + IsRunning = false; + IsConnected = false; + HasExited = true; + } + evnt = TargetExited; + break; + case TargetEventType.TargetHitBreakpoint: + lock (slock) { + IsRunning = false; + args.IsStopEvent = true; + } + evnt = TargetHitBreakpoint; + break; + case TargetEventType.TargetInterrupted: + lock (slock) { + IsRunning = false; + args.IsStopEvent = true; + } + evnt = TargetInterrupted; + break; + case TargetEventType.TargetSignaled: + lock (slock) { + IsRunning = false; + args.IsStopEvent = true; + } + evnt = TargetSignaled; + break; + case TargetEventType.TargetStopped: + lock (slock) { + IsRunning = false; + args.IsStopEvent = true; + } + evnt = TargetStopped; + break; + case TargetEventType.UnhandledException: + lock (slock) { + IsRunning = false; + args.IsStopEvent = true; + } + evnt = TargetUnhandledException; + break; + case TargetEventType.TargetReady: + evnt = TargetReady; + break; + case TargetEventType.ThreadStarted: + evnt = TargetThreadStarted; + break; + case TargetEventType.ThreadStopped: + evnt = TargetThreadStopped; + break; + } + // Set activeThread only when event is IsStopEvent(stopping execution) + // otherwise ThreadDeath(as example) event can set activeThread to dying thread + // resulting in invalid activeThread being set while process is paused. + // This happens often when using ThreadPool because after breakpoint is hit and + // process is paused threadpool kills threads since they are not in use... + if (args.Thread != null && args.IsStopEvent) + activeThread = args.Thread; + + if (HasExited || args.IsStopEvent) { + var timer = Interlocked.Exchange (ref stepTimer, null); + if (timer != null) { + timer.Stop (true); + timer.Dispose (); + } + } + + evnt?.Invoke (this, args); + + TargetEvent?.Invoke (this, args); + } + + internal void OnRunning () + { + IsRunning = true; + TargetStarted?.Invoke (this, EventArgs.Empty); + } + + internal protected void OnStarted () + { + OnStarted (null); + } + + internal protected virtual void OnStarted (ThreadInfo t) + { + if (HasExited) + return; + + OnTargetEvent (new TargetEventArgs (TargetEventType.TargetReady) { Thread = t }); + + lock (slock) { + if (!HasExited) { + IsConnected = true; + if (breakpointStore != null) { + foreach (BreakEvent bp in breakpointStore) + AddBreakEvent (bp); + } + } + } + } + + internal protected void OnTargetOutput (bool isStderr, string text) + { + OutputWriter?.Invoke (isStderr, text); + } + + internal protected void OnDebuggerOutput (bool isStderr, string text) + { + LogWriter?.Invoke (isStderr, text); + } + + internal protected void OnTargetDebug (int level, string category, string message) + { + var writer = DebugWriter; + + if (writer != null) { + writer (level, category, message); + } else { + OnDebuggerOutput (false, string.Format ("[{0}:{1}] {2}", level, category, message)); + } + } + + internal protected void OnAssemblyLoaded (string assemblyLocation) + { + var assembly = new Assembly (assemblyLocation); + OnAssemblyLoaded (assembly); + } + + internal protected void OnAssemblyLoaded (Assembly assembly) + { + lock (assemblies) { + assemblies.Add (assembly); + } + + AssemblyLoaded?.Invoke (this, new AssemblyEventArgs (assembly)); + } + + internal protected void SetBusyState (BusyStateEventArgs args) + { + BusyStateChanged?.Invoke (this, args); + } + + internal protected void OnSubprocessStarted (DebuggerSession session) + { + SubprocessStarted?.Invoke (this, new SubprocessStartedEventArgs { SubprocessSession = session }); + } + + /// + /// Tries to bind all unbound breakpoints of a source file + /// + /// + /// Source file path + /// + /// + /// This method can be called by a subclass to ask the debugger session to attempt + /// to bind all unbound breakpoints defined on the given file. This method could + /// be called, for example, when a new assembly that contains this file is loaded + /// into memory. It is not necessary to use this method if the subclass keeps + /// track of unbound breakpoints by itself. + /// + internal protected void BindSourceFileBreakpoints (string fullFilePath) + { + Dictionary breakpointsCopy; + StringComparer comparer; + + if (System.IO.Path.DirectorySeparatorChar == '\\') + comparer = StringComparer.InvariantCultureIgnoreCase; + else + comparer = StringComparer.InvariantCulture; + + lock (breakpoints) { + // Make a copy of the breakpoints table since it can be modified while iterating + breakpointsCopy = new Dictionary (breakpoints); + } + + foreach (var bps in breakpointsCopy) { + if (bps.Key is Breakpoint bp && comparer.Compare (System.IO.Path.GetFullPath (bp.FileName), fullFilePath) == 0) + RetryEventBind (bps.Value); + } + } + + void RetryEventBind (BreakEventInfo binfo) + { + // Try inserting the breakpoint again + var be = binfo.BreakEvent; + + try { + binfo = OnInsertBreakEvent (be); + if (binfo == null) + throw new InvalidOperationException ("OnInsertBreakEvent can't return a null value. If the breakpoint can't be bound or is invalid, a BreakEventInfo with the corresponding status must be returned"); + lock (breakpoints) { + breakpoints [be] = binfo; + } + binfo.AttachSession (this, be); + } catch (Exception ex) { + if (be is Breakpoint bp) + OnDebuggerOutput (false, "Could not set breakpoint at location '" + bp.FileName + ":" + bp.Line + " (" + ex.Message + ")\n"); + else + OnDebuggerOutput (false, "Could not set catchpoint for exception '" + ((Catchpoint)be).ExceptionName + "' (" + ex.Message + ")\n"); + HandleException (ex); + } + } + + /// + /// Unbinds all bound breakpoints of a source file + /// + /// + /// The source file path + /// + /// + /// This method can be called by a subclass to ask the debugger session to + /// unbind all bound breakpoints defined on the given file. This method could + /// be called, for example, when an assembly that contains this file is unloaded + /// from memory. It is not necessary to use this method if the subclass keeps + /// track of unbound breakpoints by itself. + /// + internal protected void UnbindSourceFileBreakpoints (string fullFilePath) + { + var toUpdate = new List (); + + lock (breakpoints) { + foreach (var bps in breakpoints) { + if (bps.Key is Breakpoint bp && bps.Value.Status == BreakEventStatus.Bound) { + if (System.IO.Path.GetFullPath (bp.FileName) == fullFilePath) + toUpdate.Add (bps.Value); + } + } + + foreach (var be in toUpdate) + breakpoints.Remove (be.BreakEvent); + } + + foreach (var be in toUpdate) + NotifyBreakEventStatusChanged (be.BreakEvent); + } + + internal void NotifyBreakEventStatusChanged (BreakEvent be) + { + var s = GetBreakEventStatus (be); + if (s == BreakEventStatus.BindError || s == BreakEventStatus.Invalid) + OnDebuggerOutput (true, GetBreakEventErrorMessage (be) + ": " + GetBreakEventStatusMessage (be) + "\n"); + Breakpoints.NotifyStatusChanged (be); + } + + static string GetBreakEventErrorMessage (BreakEvent be) + { + if (be is Breakpoint bp) + return string.Format ("Could not insert breakpoint at '{0}:{1}'", bp.FileName, bp.Line); + + var cp = (Catchpoint) be; + + return string.Format ("Could not enable catchpoint for exception '{0}'", cp.ExceptionName); + } + + /// + /// Reports an unhandled exception in the debugger + /// + /// + /// True if the debugger engine handles the exception. False otherwise. + /// + /// + /// The exception + /// + /// + /// This method can be used by subclasses to report errors in the debugger that must be reported + /// to the user. + /// + protected virtual bool HandleException (Exception ex) + { + if (ExceptionHandler != null) + return ExceptionHandler (ex); + + return false; + } + + internal void AdjustBreakpointLocation (Breakpoint b, int newLine, int newColumn) + { + lock (slock) { + lock (breakpoints) { + try { + adjustingBreakpoints = true; + Breakpoints.AdjustBreakpointLine (b, newLine, newColumn); + } finally { + adjustingBreakpoints = false; + } + } + } + } + + /// + /// When set, operations such as OnRun, OnAttachToProcess, OnStepLine, etc, are run in + /// a background thread, so it will not block the caller of the corresponding public methods. + /// + protected bool UseOperationThread { get; set; } + + /// + /// Called to start the execution of the debugger + /// + /// + /// Startup information + /// + protected abstract void OnRun (DebuggerStartInfo startInfo); + + /// + /// Called to attach the debugger to a running process + /// + /// + /// Process identifier. + /// + protected abstract void OnAttachToProcess (long processId); + + /// + /// Called to attach the debugger to a running process + /// + /// + /// Process identifier. + /// + protected virtual void OnAttachToProcess (ProcessInfo processInfo) + { + OnAttachToProcess (processInfo.Id); + } + + /// + /// Called to detach the debugging session from the running process + /// + protected abstract void OnDetach (); + + /// + /// Called when the active thread has to be changed + /// + /// + /// Process identifier. + /// + /// + /// Thread identifier. + /// + /// + /// This method can only be called when the debuggee is stopped by the debugger + /// + protected abstract void OnSetActiveThread (long processId, long threadId); + + /// + /// Called when the debug session has to be paused + /// + protected abstract void OnStop (); + + /// + /// Called when the target process has to be exited + /// + protected abstract void OnExit (); + + + /// + /// Called to step one source code line + /// + /// + /// This method can only be called when the debuggee is stopped by the debugger + /// + protected abstract void OnStepLine (); + + /// + /// Called to step one source line, but step over method calls + /// + /// + /// This method can only be called when the debuggee is stopped by the debugger + /// + protected abstract void OnNextLine (); + + /// + /// Called to step one instruction + /// + /// + /// This method can only be called when the debuggee is stopped by the debugger + /// + protected abstract void OnStepInstruction (); + + /// + /// Called to step one instruction, but step over method calls + /// + /// + /// This method can only be called when the debuggee is stopped by the debugger + /// + protected abstract void OnNextInstruction (); + + /// + /// Called to continue execution until leaving the current method + /// + /// + /// This method can only be called when the debuggee is stopped by the debugger + /// + protected abstract void OnFinish (); + + /// + /// Called to resume execution + /// + /// + /// This method can only be called when the debuggee is stopped by the debugger + /// + protected abstract void OnContinue (); + + /// + /// Checks whether or not the debugger supports setting the next statement to use when the debugger is resumed. + /// + /// + /// This method is generally used to determine whether or not UI menu items should be shown. + /// + /// true if the debugger supports setting the next statement to use when the debugger is resumed; otherwise, false. + public virtual bool CanSetNextStatement { + get { return false; } + } + + /// + /// Sets the next statement to be executed when the debugger is resumed. + /// + /// + /// This method can only be called when the debuggee is stopped by the debugger. + /// + protected virtual void OnSetNextStatement (long threadId, string fileName, int line, int column) + { + throw new NotSupportedException (); + } + + /// + /// Sets the next statement to be executed when the debugger is resumed. + /// + /// + /// This method can only be called when the debuggee is stopped by the debugger. + /// + protected virtual void OnSetNextStatement (long threadId, int ilOffset) + { + throw new NotSupportedException (); + } + + //breakpoints etc + + /// + /// Called to insert a new breakpoint or catchpoint + /// + /// + /// The break event. + /// + /// + /// Implementations of this method must: (1) create (and return) an instance of BreakEventInfo + /// (or a subclass of it). (2) Attempt to activate a breakpoint at the location + /// specified in breakEvent. If the breakpoint cannot be activated at the time this + /// method is called, it is the responsibility of the DebuggerSession subclass + /// to attempt it later on. + /// The BreakEventInfo object can be used to change the status of the breakpoint, + /// update the hit point, etc. + /// + protected abstract BreakEventInfo OnInsertBreakEvent (BreakEvent breakEvent); + + /// + /// Called when a breakpoint has been removed. + /// + /// + /// The breakpoint + /// + /// + /// Implementations of this method should remove or disable the breakpoint + /// in the debugging engine. + /// + protected abstract void OnRemoveBreakEvent (BreakEventInfo eventInfo); + + /// + /// Called when information about a breakpoint has changed + /// + /// + /// The break event + /// + /// + /// This method is called when some information about the breakpoint changes. + /// Notice that the file and line of a breakpoint or the exception name of + /// a catchpoint can't be modified. Changes of the Enabled property are + /// notified by calling OnEnableBreakEvent. + /// + protected abstract void OnUpdateBreakEvent (BreakEventInfo eventInfo); + + /// + /// Called when a break event is enabled or disabled + /// + /// + /// The break event + /// + /// + /// The new status + /// + protected abstract void OnEnableBreakEvent (BreakEventInfo eventInfo, bool enable); + + /// + /// Queried when the debugger session has to check if changes in breakpoints are allowed or not + /// + /// + /// true if break event changes are allowed; otherwise, false. + /// + /// + /// This property should return false if at the time it is invoked the debugger engine doesn't support + /// adding, removing or doing changes in breakpoints. + /// + protected virtual bool AllowBreakEventChanges { get { return true; } } + + /// + /// Called to get a list of the threads of a process + /// + /// + /// Process identifier. + /// + /// + /// This method can only be called when the debuggee is stopped by the debugger + /// + protected abstract ThreadInfo[] OnGetThreads (long processId); + + /// + /// Called to get a list of all debugee processes + /// + /// + /// This method can only be called when the debuggee is stopped by the debugger + /// + protected abstract ProcessInfo[] OnGetProcesses (); + + /// + /// Called to get the backtrace of a thread + /// + /// + /// Process identifier. + /// + /// + /// Thread identifier. + /// + /// + /// This method can only be called when the debuggee is stopped by the debugger + /// + protected abstract Backtrace OnGetThreadBacktrace (long processId, long threadId); + + /// + /// Called to get the elapsedTime of last step of a thread in milliseconds + /// + /// + /// Process identifier. + /// + /// + /// Thread identifier. + /// + /// + /// This method can only be called when the debuggee is stopped by the debugger + /// This method will return -1 if time measurement is not available + /// + protected virtual long OnGetElapsedTime (long processId, long threadId) + { + return -1; + } + + /// + /// Called to gets the disassembly of a source code file + /// + /// + /// An array of AssemblyLine, with one element for each source code line that could be disassembled + /// + /// + /// The file. + /// + /// + /// This method can only be used when the debuggee is stopped by the debugger + /// + protected virtual AssemblyLine[] OnDisassembleFile (string file) + { + return null; + } + + internal T WrapDebuggerObject (T obj) where T:class,IDebuggerBackendObject + { + return obj != null ? OnWrapDebuggerObject (obj) : null; + } + + /// + /// Called for every object that is obtained from the debugger engine. + /// Subclasses may want to create wrappers for some of those objects + /// + protected virtual T OnWrapDebuggerObject (T obj) where T:class,IDebuggerBackendObject + { + return obj; + } + + protected IDebuggerSessionFrontend Frontend { + get { + return frontend; + } + } + + protected virtual void OnFetchFrames (ThreadInfo[] threads) + { + } + + /// + /// Calling this method is optional. + /// It's usefull to call this method so underlying debugger can optimize speed of fetching multiple frames on . + /// + /// Array of threads to fetch frames. + public void FetchFrames (ThreadInfo[] threads) + { + OnFetchFrames (threads); + } + } + + class InternalDebuggerSession: IDebuggerSessionFrontend + { + readonly DebuggerSession session; + + public InternalDebuggerSession (DebuggerSession session) + { + this.session = session; + } + + public void NotifyTargetEvent (TargetEventArgs args) + { + session.OnTargetEvent (args); + } + + public void NotifyTargetOutput (bool isStderr, string text) + { + session.OnTargetOutput (isStderr, text); + } + + public void NotifyDebuggerOutput (bool isStderr, string text) + { + session.OnDebuggerOutput (isStderr, text); + } + + public void NotifyStarted (ThreadInfo t) + { + session.OnStarted (t); + } + + public void NotifyStarted () + { + session.OnStarted (); + } + + public void BindSourceFileBreakpoints (string fullFilePath) + { + session.BindSourceFileBreakpoints (fullFilePath); + } + + public void UnbindSourceFileBreakpoints (string fullFilePath) + { + session.UnbindSourceFileBreakpoints (fullFilePath); + } + } + + public delegate void OutputWriterDelegate (bool isStderr, string text); + public delegate void DebugWriterDelegate (int level, string category, string message); + + public class BusyStateEventArgs: EventArgs + { + public bool IsBusy { get; internal set; } + + public string Description { get; internal set; } + + public EvaluationContext EvaluationContext { get; internal set; } + } + + public interface IConnectionDialog : IDisposable + { + event EventHandler UserCancelled; + + //message may be null in which case the dialog should construct a default + void SetMessage (DebuggerStartInfo dsi, string message, bool listening, int attemptNumber); + } + + public class SubprocessStartedEventArgs : EventArgs + { + public DebuggerSession SubprocessSession { get; set; } + } +} + diff --git a/VisualPascalABCNETLinux/MonoDebugging/Client/DebuggerSessionOptions.cs b/VisualPascalABCNETLinux/MonoDebugging/Client/DebuggerSessionOptions.cs new file mode 100644 index 000000000..7ef5dc3e7 --- /dev/null +++ b/VisualPascalABCNETLinux/MonoDebugging/Client/DebuggerSessionOptions.cs @@ -0,0 +1,73 @@ +// +// DebuggerSessionOptions.cs +// +// Author: +// Lluis Sanchez Gual +// +// Copyright (c) 2009 Novell, Inc (http://www.novell.com) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +using System; +using System.Collections.Generic; + + +namespace Mono.Debugging.Client +{ + [Serializable] + public enum AutomaticSourceDownload + { + Ask, + Always, + Never + } + + [Serializable] + public class DebuggerSessionOptions + { + public EvaluationOptions EvaluationOptions { get; set; } + public bool StepOverPropertiesAndOperators { get; set; } + public bool ProjectAssembliesOnly { get; set; } + public AutomaticSourceDownload AutomaticSourceLinkDownload { get; set; } + public bool DebugSubprocesses { get; set; } + public List SymbolSearchPaths { get; set; } = new List(); + public bool SearchMicrosoftSymbolServer { get; set; } + public bool SearchNuGetSymbolServer { get; set; } + } + + [Serializable] + public class SymbolSource + { + public SymbolSource () + { + // For deserialization + } + + public SymbolSource (string path, string name, bool isEnabled) + { + Path = path; + Name = name; + IsEnabled = isEnabled; + } + + public string Name { get; set; } + public string Path { get; set; } + public bool IsEnabled { get; set; } + } +} diff --git a/VisualPascalABCNETLinux/MonoDebugging/Client/DebuggerStartInfo.cs b/VisualPascalABCNETLinux/MonoDebugging/Client/DebuggerStartInfo.cs new file mode 100644 index 000000000..fc7016299 --- /dev/null +++ b/VisualPascalABCNETLinux/MonoDebugging/Client/DebuggerStartInfo.cs @@ -0,0 +1,92 @@ +// DebuggerStartInfo.cs +// +// Author: +// Lluis Sanchez Gual +// +// Copyright (c) 2008 Novell, Inc (http://www.novell.com) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// +// + +using System; +using System.Collections.Generic; + +namespace Mono.Debugging.Client +{ + [Serializable] + public class DebuggerStartInfo + { + string command; + string arguments; + string runtimeArguments; + string workingDirectory; + Dictionary environmentVariables; + + public string Command { + get { + return command; + } + set { + command = value; + } + } + + public string Arguments { + get { + return arguments; + } + set { + arguments = value; + } + } + + public string RuntimeArguments { + get { + return runtimeArguments; + } + set { + runtimeArguments = value; + } + } + + public string WorkingDirectory { + get { + return workingDirectory; + } + set { + workingDirectory = value; + } + } + + public Dictionary EnvironmentVariables { + get { + if (environmentVariables == null) + environmentVariables = new Dictionary (); + return environmentVariables; + } + } + + public bool UseExternalConsole { get; set; } + + public bool CloseExternalConsoleOnExit { get; set; } + + public bool RequiresManualStart { get; set; } + } +} diff --git a/VisualPascalABCNETLinux/MonoDebugging/Client/DebuggerStatistics.cs b/VisualPascalABCNETLinux/MonoDebugging/Client/DebuggerStatistics.cs new file mode 100644 index 000000000..5b2c9e382 --- /dev/null +++ b/VisualPascalABCNETLinux/MonoDebugging/Client/DebuggerStatistics.cs @@ -0,0 +1,201 @@ +// +// DebuggerStatistics.cs +// +// Author: +// Jeffrey Stedfast +// +// Copyright (c) 2019 Microsoft Corp. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +using System; +using System.Diagnostics; +using System.Collections.Generic; +using System.Linq; +using System.Threading; + +namespace Mono.Debugging.Client +{ + public class DebuggerStatistics + { + static readonly int[] UpperTimeLimits = { 10, 50, 100, 250, 500, 1000, 1500, 2000, 5000, 10000 }; + + readonly int[] buckets = new int[UpperTimeLimits.Length + 1]; + readonly object mutex = new object (); + TimeSpan minTime = TimeSpan.MaxValue; + TimeSpan maxTime, totalTime; + + public DebuggerStatistics(string name) + { + Name = name; + } + + public double MaxTime { + get { return maxTime.TotalMilliseconds; } + } + + public double MinTime { + get { return minTime.TotalMilliseconds; } + } + + public double AverageTime { + get { + if (TimingsCount > 0) + return totalTime.TotalMilliseconds / TimingsCount; + + return 0; + } + } + + public int TimingsCount { get; private set; } + + public int FailureCount { get; private set; } + public string Name { get; } + + public DebuggerTimer StartTimer (string name) + { + return new DebuggerTimer (this, name); + } + + int GetBucketIndex (TimeSpan duration) + { + var ms = (long) duration.TotalMilliseconds; + + for (var bucket = 0; bucket < UpperTimeLimits.Length; bucket++) { + if (ms <= UpperTimeLimits[bucket]) + return bucket; + } + + return buckets.Length - 1; + } + + public void AddTime (TimeSpan duration) + { + lock (mutex) { + if (duration > maxTime) + maxTime = duration; + + if (duration < minTime) + minTime = duration; + + buckets[GetBucketIndex (duration)]++; + + totalTime += duration; + TimingsCount++; + } + } + + public void IncrementFailureCount () + { + lock (mutex) { + FailureCount++; + } + } + + public void Serialize (Dictionary metadata) + { + metadata["AverageDuration"] = AverageTime; + metadata["MaximumDuration"] = MaxTime; + metadata["MinimumDuration"] = MinTime; + metadata["FailureCount"] = FailureCount; + metadata["SuccessCount"] = TimingsCount; + + for (int i = 0; i < buckets.Length; i++) + metadata[$"Bucket{i}"] = buckets[i]; + } + } + + public class DebuggerTimer : IDisposable + { + readonly DebuggerStatistics stats; + readonly Stopwatch stopwatch; + + static readonly TraceListener traceListener = new TraceSource (nameof (DebuggerTimer)).Listeners.OfType ().FirstOrDefault (l => l.Name == nameof (DebuggerTimer)); + static int traceSeq = 0; + int traceId; + + public DebuggerTimer (DebuggerStatistics stats, string name) + { + stopwatch = Stopwatch.StartNew (); + this.stats = stats; + if (stats != null && traceListener != null) { + traceId = Interlocked.Increment (ref traceSeq); + traceListener.TraceEvent (null, stats.Name, TraceEventType.Start, traceId, name); + } + } + + /// + /// Indicates if the debugger operation was successful. If this is false the + /// timing will not be reported and a failure will be indicated. + /// + public bool Success { get; set; } + + public TimeSpan Elapsed { + get { return stopwatch.Elapsed; } + } + + public void Stop (bool success) + { + stopwatch.Stop (); + Success = success; + + if (stats == null) + return; + traceListener?.TraceEvent (null, stats.Name, TraceEventType.Stop, traceId, "Stop1"); + + if (success) + stats.AddTime (stopwatch.Elapsed); + else + stats.IncrementFailureCount (); + } + + public void Stop (ObjectValue val) + { + stopwatch.Stop (); + if (stats == null) + return; + traceListener?.TraceEvent (null, stats.Name, TraceEventType.Stop, traceId, "Stop2"); + + if (val.IsEvaluating || val.IsEvaluatingGroup) { + // Do not capture timing - evaluation not finished. + } else if (val.IsError || val.IsImplicitNotSupported || val.IsNotSupported || val.IsUnknown) { + stats.IncrementFailureCount (); + } else { + // Success + stats.AddTime (stopwatch.Elapsed); + } + } + + public void Dispose () + { + if (stopwatch.IsRunning) { + stopwatch.Stop (); + + if (stats == null) + return; + traceListener?.TraceEvent (null, stats.Name, TraceEventType.Stop, traceId, "Dispose"); + + if (Success) + stats.AddTime (stopwatch.Elapsed); + else + stats.IncrementFailureCount (); + } + } + } +} diff --git a/VisualPascalABCNETLinux/MonoDebugging/Client/EvaluationOptions.cs b/VisualPascalABCNETLinux/MonoDebugging/Client/EvaluationOptions.cs new file mode 100644 index 000000000..1475ddb99 --- /dev/null +++ b/VisualPascalABCNETLinux/MonoDebugging/Client/EvaluationOptions.cs @@ -0,0 +1,149 @@ +// +// EvaluationOptions.cs +// +// Author: +// Lluis Sanchez Gual +// +// Copyright (c) 2009 Novell, Inc (http://www.novell.com) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +using System; + +namespace Mono.Debugging.Client +{ + [Serializable] + public class EvaluationOptions + { + bool allowMethodEvaluation; + bool allowToStringCalls; + + public const char Ellipsis = '…'; + + public static EvaluationOptions DefaultOptions { + get { + EvaluationOptions ops = new EvaluationOptions (); + ops.EvaluationTimeout = 1000; + ops.MemberEvaluationTimeout = 5000; + ops.AllowTargetInvoke = true; + ops.AllowMethodEvaluation = true; + ops.AllowToStringCalls = true; + ops.FlattenHierarchy = true; + ops.GroupPrivateMembers = true; + ops.GroupStaticMembers = true; + ops.UseExternalTypeResolver = true; + ops.IntegerDisplayFormat = IntegerDisplayFormat.Decimal; + ops.CurrentExceptionTag = "$exception"; + ops.EllipsizeStrings = true; + ops.EllipsizedLength = 100; + ops.ChunkRawStrings = false; + ops.StackFrameFormat = new StackFrameFormat (); + return ops; + } + } + + public EvaluationOptions Clone () + { + var clone = (EvaluationOptions)MemberwiseClone (); + clone.StackFrameFormat = new StackFrameFormat (StackFrameFormat); + return clone; + } + + public bool ChunkRawStrings { get; set; } + + public bool EllipsizeStrings { get; set; } + public int EllipsizedLength { get; set; } + + public int EvaluationTimeout { get; set; } + public int MemberEvaluationTimeout { get; set; } + public bool AllowTargetInvoke { get; set; } + + public bool AllowMethodEvaluation { + get { return allowMethodEvaluation && AllowTargetInvoke; } + set { allowMethodEvaluation = value; } + } + + public bool AllowToStringCalls { + get { return allowToStringCalls && AllowTargetInvoke; } + set { allowToStringCalls = value; } + } + + public bool AllowDisplayStringEvaluation { + get { return AllowTargetInvoke; } + } + + public bool AllowDebuggerProxy { + get { return AllowTargetInvoke; } + } + + public bool FlattenHierarchy { get; set; } + + public bool GroupPrivateMembers { get; set; } + + public bool GroupStaticMembers { get; set; } + + public bool UseExternalTypeResolver { get; set; } + + public IntegerDisplayFormat IntegerDisplayFormat { get; set; } + + public string CurrentExceptionTag { get; set; } + + public bool IEnumerable { get; set; } + + public StackFrameFormat StackFrameFormat { get; set; } + } + + public class StackFrameFormat + { + public bool Line { get; set; } = true; + + public bool Module { get; set; } = false; + + public bool ParameterNames { get; set; } = true; + + public bool ParameterTypes { get; set; } = true; + + public bool ParameterValues { get; set; } = false; + + /// + /// Default is null. Which means do same as "ProjectAssembliesOnly" setting. + /// + public bool? ExternalCode { get; set; } = null; + + public StackFrameFormat () + { + } + + public StackFrameFormat (StackFrameFormat copy) + { + Line = copy.Line; + Module = copy.Module; + ParameterNames = copy.ParameterNames; + ParameterTypes = copy.ParameterTypes; + ParameterValues = copy.ParameterValues; + ExternalCode = copy.ExternalCode; + } + } + + public enum IntegerDisplayFormat + { + Decimal, + Hexadecimal + } +} diff --git a/VisualPascalABCNETLinux/MonoDebugging/Client/ExceptionInfo.cs b/VisualPascalABCNETLinux/MonoDebugging/Client/ExceptionInfo.cs new file mode 100644 index 000000000..700e0e561 --- /dev/null +++ b/VisualPascalABCNETLinux/MonoDebugging/Client/ExceptionInfo.cs @@ -0,0 +1,320 @@ +// +// ExceptionInfo.cs +// +// Author: +// Lluis Sanchez Gual +// +// Copyright (c) 2010 Novell, Inc (http://www.novell.com) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +using System; +using System.Collections.Generic; +using System.Text; + +namespace Mono.Debugging.Client +{ + [Serializable] + public class ExceptionInfo + { + ObjectValue exception; + ObjectValue messageObject; + + [NonSerialized] + ExceptionStackFrame[] frames; + + [NonSerialized] + ExceptionInfo innerException; + + [NonSerialized] + ObjectValue instance; + + /// + /// The provided value can have the following members: + /// Type of the object: type of the exception + /// Message: Message of the exception + /// Instance: Raw instance of the exception + /// StackTrace: an array of frames. Each frame must have: + /// Value of the object: display text of the frame + /// File: name of the file + /// Line: line + /// Column: column + /// InnerException: inner exception, following the same format described above. + /// + public ExceptionInfo (ObjectValue exception) + { + this.exception = exception; + if (exception.IsEvaluating || exception.IsEvaluatingGroup) + exception.ValueChanged += HandleExceptionValueChanged; + } + + void LoadMessage () + { + if (messageObject == null) { + messageObject = exception.GetChild ("Message"); + if (messageObject != null && messageObject.IsEvaluating) + messageObject.ValueChanged += HandleMessageValueChanged; + } + } + + void HandleMessageValueChanged (object sender, EventArgs e) + { + frames = null; + NotifyChanged (); + } + + void HandleExceptionValueChanged (object sender, EventArgs e) + { + frames = null; + if (exception.IsEvaluatingGroup) + exception = exception.GetArrayItem (0); + LoadMessage (); + NotifyChanged (); + } + + void NotifyChanged () + { + EventHandler evnt = Changed; + if (evnt != null) + evnt (this, EventArgs.Empty); + } + + public string Type { + get { return exception.TypeName; } + } + + public string Message { + get { + LoadMessage (); + if (messageObject != null && messageObject.IsEvaluating) + return "Loading..."; + return messageObject != null ? messageObject.Value : null; + } + } + + ObjectValue helpLinkObject; + public string HelpLink { + get { + if (helpLinkObject == null) { + helpLinkObject = exception.GetChild ("HelpLink"); + if (helpLinkObject != null && helpLinkObject.IsEvaluating) { + helpLinkObject.ValueChanged += delegate { + NotifyChanged (); + }; + } + } + return helpLinkObject != null ? helpLinkObject.Value : null; + } + } + + public ObjectValue Instance { + get { + if (instance == null) + instance = exception.GetChild ("Instance"); + if (instance == null) + return exception; + return instance; + } + } + + public bool IsEvaluating { + get { return exception.IsEvaluating || exception.IsEvaluatingGroup; } + } + + public bool StackIsEvaluating { + get { + ObjectValue stackTrace = exception.GetChild ("StackTrace"); + if (stackTrace != null) + return stackTrace.IsEvaluating; + + return false; + } + } + + public ExceptionStackFrame[] StackTrace { + get { + if (frames != null) + return frames; + + var stackTrace = exception.GetChild ("StackTrace"); + if (stackTrace == null || stackTrace.IsNull) + return frames = new ExceptionStackFrame [0]; + + if (stackTrace.IsEvaluating) { + frames = new ExceptionStackFrame [0]; + stackTrace.ValueChanged += HandleExceptionValueChanged; + return frames; + } + + if (stackTrace.TypeName == "string") { + stackTrace = Debugging.Evaluation.ExceptionInfoSource.GetStackTrace (stackTrace.Value); + } + + if (!stackTrace.IsArray) { + return frames = new ExceptionStackFrame [0]; + } + + var list = new List (); + for (int i = 0; i < stackTrace.ArrayCount; i++) + list.Add (new ExceptionStackFrame (stackTrace.GetArrayItem (i, EvaluationOptions.DefaultOptions))); + + frames = list.ToArray (); + + return frames; + } + } + + public ExceptionInfo InnerException { + get { + if (innerException == null) { + ObjectValue innerVal = exception.GetChild ("InnerException"); + if (innerVal == null || innerVal.IsNull || innerVal.IsError || innerVal.IsUnknown) + return null; + if (innerVal.IsEvaluating) { + innerVal.ValueChanged += delegate { NotifyChanged (); }; + return null; + } + innerException = new ExceptionInfo (innerVal); + innerException.Changed += delegate { + NotifyChanged (); + }; + } + return innerException; + } + } + + List innerExceptions; + public List InnerExceptions { + get { + if (innerExceptions == null) { + ObjectValue innerVal = exception.GetChild ("InnerExceptions"); + if (innerVal == null || innerVal.IsError || innerVal.IsUnknown) + return null; + if (innerVal.IsEvaluating) { + innerVal.ValueChanged += delegate { NotifyChanged (); }; + return null; + } + innerExceptions = new List (); + foreach (var inner in innerVal.GetAllChildren ()) { + var innerObj = new ExceptionInfo (inner); + innerObj.Changed += delegate { + NotifyChanged (); + }; + innerExceptions.Add (innerObj); + } + } + return innerExceptions; + } + } + + public event EventHandler Changed; + + internal void ConnectCallback (StackFrame parentFrame) + { + ObjectValue.ConnectCallbacks (parentFrame, exception); + } + + public override string ToString () + { + var sb = new StringBuilder (); + var chain = new List (); + var e = this; + + while (e != null) { + chain.Insert (0, e); + if (sb.Length > 0) + sb.Append (" ---> "); + sb.Append (e.Type).Append (": ").Append (e.Message); + e = e.InnerException; + } + + sb.AppendLine (); + + foreach (var ex in chain) { + if (ex != chain[0]) + sb.AppendLine (" --- End of inner exception stack trace ---"); + + foreach (var frame in ex.StackTrace) { + sb.Append (" at ").Append (frame.DisplayText); + + if (!string.IsNullOrEmpty (frame.File)) { + sb.Append (" in ").Append (frame.File).Append (':').Append (frame.Line); + if (frame.Column != 0) + sb.Append (',').Append (frame.Column); + } + + sb.AppendLine (); + } + } + + return sb.ToString (); + } + } + + public class ExceptionStackFrame + { + readonly ObjectValue frame; + + /// + /// The provided value must have a specific structure. + /// The Value property is the display text. + /// A child "File" member must be the name of the file. + /// A child "Line" member must be the line. + /// A child "Column" member must be the column. + /// + public ExceptionStackFrame (ObjectValue value) + { + frame = value; + } + + public string File { + get { + var file = frame.GetChild ("File", EvaluationOptions.DefaultOptions); + if (file != null) + return file.Value; + + return null; + } + } + + public int Line { + get { + var val = frame.GetChild ("Line", EvaluationOptions.DefaultOptions); + if (val != null) + return int.Parse (val.Value); + + return 0; + } + } + + public int Column { + get { + var val = frame.GetChild ("Column", EvaluationOptions.DefaultOptions); + if (val != null) + return int.Parse (val.Value); + + return 0; + } + } + + public string DisplayText { + get { return frame.Value; } + } + } +} diff --git a/VisualPascalABCNETLinux/MonoDebugging/Client/FunctionBreakpoint.cs b/VisualPascalABCNETLinux/MonoDebugging/Client/FunctionBreakpoint.cs new file mode 100644 index 000000000..7ed3998bf --- /dev/null +++ b/VisualPascalABCNETLinux/MonoDebugging/Client/FunctionBreakpoint.cs @@ -0,0 +1,233 @@ +// +// FunctionBreakpoint.cs +// +// Author: Jeffrey Stedfast +// +// Copyright (c) 2012 Xamarin Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +using System; +using System.Xml; +using System.Collections.Generic; + +namespace Mono.Debugging.Client +{ + + [Serializable] + public class FunctionBreakpoint : Breakpoint + { + public FunctionBreakpoint (string functionName, string language) : base (null, 1, 1) + { + Language = language; + ParseTypeAndMethodName (functionName); + } + + private void ParseTypeAndMethodName (string functionName) + { + FunctionName = functionName; + var bracket = functionName.IndexOf ('('); + int dot; + if (bracket != -1) { + //Handle stuff like SomeNamespace.SomeType.Method(SomeOtherNamespace.SomeOtherType) + dot = functionName.LastIndexOf('.', bracket); + } else { + dot = functionName.LastIndexOf('.'); + } + if (dot == -1 || dot + 1 == functionName.Length) + return; + + // FIXME: handle types like GenericType<>, GenericType, and GenericType<...>+NestedGenricType<...> + string methodName; + string typeName = functionName.Substring (0, dot); + if (bracket == -1) { + methodName = functionName.Substring (dot + 1); + } else { + methodName = functionName.Substring (dot + 1, bracket - (dot + 1)); + } + TypeName = typeName; + MethodName = methodName; + } + + public FunctionBreakpoint (string typeName, string methodName, string language) : base (null, 1, 1) + { + Language = language; + TypeName = typeName; + MethodName = methodName; + FunctionName = TypeName + "." + MethodName; + } + + static bool SkipArrayRank (string text, ref int index, int endIndex) + { + char c; + + while (index < endIndex) { + if ((c = text[index++]) == ']') + return true; + + if (c != ',' && !char.IsWhiteSpace (c) && !char.IsDigit (c)) + return false; + } + + return false; + } + + static bool SkipGenericArgs (string text, ref int index, int endIndex) + { + char c; + + while (index < endIndex) { + if ((c = text[index++]) == '>') + return true; + + if (c == '<') { + if (!SkipGenericArgs (text, ref index, endIndex)) + return false; + + while (index < endIndex && !char.IsWhiteSpace (text[index])) + index++; + + // the only chars allowed after a '>' are: '>', '[', and ',' + if (">[,".IndexOf (text[index]) == -1) + return false; + } else if (c == '[') { + if (!SkipArrayRank (text, ref index, endIndex)) + return false; + + while (index < endIndex && !char.IsWhiteSpace (text[index])) + index++; + + // the only chars allowed after a ']' are: '>', '[', and ',' + if (">[,".IndexOf (text[index]) == -1) + return false; + } else if (c != '.' && c != '+' && c != ',' && !char.IsWhiteSpace (c) && !char.IsLetterOrDigit (c)) { + return false; + } + } + + return false; + } + + public static bool TryParseParameters (string text, int startIndex, int endIndex, out string[] paramTypes) + { + List parsedParamTypes = new List (); + int index = startIndex; + int start; + char c; + + paramTypes = null; + + while (index < endIndex) { + while (char.IsWhiteSpace (text[index])) + index++; + + start = index; + while (index < endIndex) { + if ((c = text[index]) == ',') + break; + + index++; + + if (c == '<') { + if (!SkipGenericArgs (text, ref index, endIndex)) + return false; + } else if (c == '[') { + if (!SkipArrayRank (text, ref index, endIndex)) + return false; + } + } + + parsedParamTypes.Add (text.Substring (start, index - start).TrimEnd ()); + + if (index < endIndex) + index++; + } + + paramTypes = parsedParamTypes.ToArray (); + + return true; + } + + internal FunctionBreakpoint (XmlElement elem, string baseDir) : base (elem, baseDir) + { + var functionName = elem.GetAttribute ("function"); + ParseTypeAndMethodName (functionName); + string text = elem.GetAttribute ("language"); + if (string.IsNullOrEmpty (text)) + Language = "C#"; + else + Language = text; + + if (elem.HasAttribute ("params")) { + string[] paramTypes; + + text = elem.GetAttribute ("params").Trim (); + if (text.Length > 0 && TryParseParameters (text, 0, text.Length, out paramTypes)) + ParamTypes = paramTypes; + else + ParamTypes = new string[0]; + } + + FileName = null; + } + + internal override XmlElement ToXml (XmlDocument doc, string baseDir) + { + XmlElement elem = base.ToXml (doc, baseDir); + + elem.SetAttribute ("function", FunctionName); + elem.SetAttribute ("language", Language); + + if (ParamTypes != null) + elem.SetAttribute ("params", string.Join (", ", ParamTypes)); + + return elem; + } + public string FunctionName { + get; set; + } + + public string TypeName { + get; set; + } + + public string MethodName { + get; set; + } + + public string Language { + get; set; + } + + public string[] ParamTypes { + get; set; + } + + public override void CopyFrom (BreakEvent ev) + { + base.CopyFrom (ev); + + var bp = (FunctionBreakpoint) ev; + FunctionName = bp.FunctionName; + MethodName = bp.MethodName; + TypeName = bp.TypeName; + ParamTypes = bp.ParamTypes; + } + } +} diff --git a/VisualPascalABCNETLinux/MonoDebugging/Client/IExpressionEvaluator.cs b/VisualPascalABCNETLinux/MonoDebugging/Client/IExpressionEvaluator.cs new file mode 100644 index 000000000..5ded85fce --- /dev/null +++ b/VisualPascalABCNETLinux/MonoDebugging/Client/IExpressionEvaluator.cs @@ -0,0 +1,36 @@ +// +// IExpressionEvaluator.cs +// +// Author: +// Carlo kok +// +// Copyright (c) 2010 RemObjects Software +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +using Mono.Debugging.Evaluation; + +namespace Mono.Debugging.Client +{ + public interface IExpressionEvaluator + { + ExpressionEvaluator Evaluator { get; } + ObjectValue[] GetLocals (StackFrame sf); + } +} diff --git a/VisualPascalABCNETLinux/MonoDebugging/Client/ObjectPath.cs b/VisualPascalABCNETLinux/MonoDebugging/Client/ObjectPath.cs new file mode 100644 index 000000000..42e26068f --- /dev/null +++ b/VisualPascalABCNETLinux/MonoDebugging/Client/ObjectPath.cs @@ -0,0 +1,101 @@ +// ObjectPath.cs +// +// Author: +// Lluis Sanchez Gual +// +// Copyright (c) 2008 Novell, Inc (http://www.novell.com) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// +// + +using System; +using System.Collections; + +namespace Mono.Debugging.Client +{ + [Serializable] + public struct ObjectPath + { + string[] path; + + public ObjectPath (params string[] path) + { + this.path = path; + } + + public string this [int n] { + get { + if (path == null) + throw new IndexOutOfRangeException (); + return path [n]; + } + } + + public int Length { + get { return path != null ? path.Length : 0; } + } + + public IEnumerable GetEnumerator () + { + if (path != null) + return path; + + return new string [0]; + } + + public ObjectPath GetSubpath (int start) + { + if (start == 0) + return this; + + var newPath = new string [path.Length - start]; + Array.Copy (path, start, newPath, 0, newPath.Length); + + return new ObjectPath (newPath); + } + + public ObjectPath Append (string name) + { + var newPath = new string [path.Length + 1]; + Array.Copy (path, newPath, path.Length); + newPath [path.Length] = name; + return new ObjectPath (newPath); + } + + public string LastName { + get { + if (Length == 0) + return ""; + + return path[path.Length - 1]; + } + } + + public string Join (string separator) + { + return string.Join (separator, path); + } + + public override string ToString () + { + return Join ("/"); + } + } +} diff --git a/VisualPascalABCNETLinux/MonoDebugging/Client/ObjectValue.cs b/VisualPascalABCNETLinux/MonoDebugging/Client/ObjectValue.cs new file mode 100644 index 000000000..ffb6c4ad9 --- /dev/null +++ b/VisualPascalABCNETLinux/MonoDebugging/Client/ObjectValue.cs @@ -0,0 +1,882 @@ +// ObjectValue.cs +// +// Authors: Lluis Sanchez Gual +// Jeffrey Stedfast +// +// Copyright (c) 2008 Novell, Inc (http://www.novell.com) +// Copyright (c) 2012 Xamarin Inc. (http://www.xamarin.com) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// +// + +using System; +using System.Threading; +using System.Collections.Generic; +using System.Linq; + +using Mono.Debugging.Backend; + +namespace Mono.Debugging.Client +{ + [Serializable] + public class ObjectValue + { + ObjectPath path; + int arrayCount = -1; + bool isNull; + string name; + string value; + string typeName; + string displayValue; + string childSelector; + ObjectValueFlags flags; + IObjectValueSource source; + IObjectValueUpdater updater; + List children; + ManualResetEvent evaluatedEvent; + + [NonSerialized] + readonly object mutex = new object (); + + [NonSerialized] + UpdateCallback updateCallback; + + [NonSerialized] + EventHandler valueChanged; + + [NonSerialized] + StackFrame parentFrame; + + static ObjectValue Create (IObjectValueSource source, ObjectPath path, string typeName) + { + var val = new ObjectValue (); + val.typeName = typeName; + val.source = source; + val.path = path; + return val; + } + + public static ObjectValue CreateObject (IObjectValueSource source, ObjectPath path, string typeName, string value, ObjectValueFlags flags, ObjectValue[] children) + { + return CreateObject (source, path, typeName, new EvaluationResult (value), flags, children); + } + + public static ObjectValue CreateObject (IObjectValueSource source, ObjectPath path, string typeName, EvaluationResult value, ObjectValueFlags flags, ObjectValue[] children) + { + var val = Create (source, path, typeName); + val.flags = flags | ObjectValueFlags.Object; + val.displayValue = value.DisplayValue; + val.value = value.Value; + + if (children != null) { + val.children = new List (); + val.children.AddRange (children); + } + + return val; + } + + public static ObjectValue CreateNullObject (IObjectValueSource source, string name, string typeName, ObjectValueFlags flags) + { + return CreateNullObject (source, new ObjectPath (name), typeName, flags); + } + + public static ObjectValue CreateNullObject (IObjectValueSource source, ObjectPath path, string typeName, ObjectValueFlags flags) + { + var val = Create (source, path, typeName); + val.flags = flags | ObjectValueFlags.Object; + val.value = "(null)"; + val.isNull = true; + return val; + } + + public static ObjectValue CreatePrimitive (IObjectValueSource source, ObjectPath path, string typeName, EvaluationResult value, ObjectValueFlags flags) + { + var val = Create (source, path, typeName); + val.flags = flags | ObjectValueFlags.Primitive; + val.displayValue = value.DisplayValue; + val.value = value.Value; + return val; + } + + public static ObjectValue CreateArray (IObjectValueSource source, ObjectPath path, string typeName, int arrayCount, ObjectValueFlags flags, ObjectValue[] children) + { + var val = Create (source, path, typeName); + val.flags = flags | ObjectValueFlags.Array; + val.value = "[" + arrayCount + "]"; + val.arrayCount = arrayCount; + + if (children != null && children.Length > 0) { + val.children = new List (); + val.children.AddRange (children); + } + + return val; + } + + public static ObjectValue CreateUnknown (IObjectValueSource source, ObjectPath path, string typeName) + { + var val = Create (source, path, typeName); + val.flags = ObjectValueFlags.Unknown | ObjectValueFlags.ReadOnly; + return val; + } + + public static ObjectValue CreateUnknown (string name) + { + return CreateUnknown (null, new ObjectPath (name), ""); + } + + public static ObjectValue CreateError (IObjectValueSource source, ObjectPath path, string typeName, string value, ObjectValueFlags flags) + { + var val = Create (source, path, typeName); + val.flags = flags | ObjectValueFlags.Error; + val.value = value; + return val; + } + + public static ObjectValue CreateImplicitNotSupported (IObjectValueSource source, ObjectPath path, string typeName, ObjectValueFlags flags) + { + var val = Create (source, path, typeName); + val.flags = flags | ObjectValueFlags.ImplicitNotSupported; + val.value = "Implicit evaluation is disabled"; + return val; + } + + public static ObjectValue CreateNotSupported (IObjectValueSource source, ObjectPath path, string typeName, string message, ObjectValueFlags flags) + { + var val = Create (source, path, typeName); + val.flags = flags | ObjectValueFlags.NotSupported; + val.value = message; + return val; + } + + public static ObjectValue CreateFatalError (string name, string message, ObjectValueFlags flags) + { + var val = new ObjectValue (); + val.flags = flags | ObjectValueFlags.Error; + val.value = message; + val.name = name; + return val; + } + + public static ObjectValue CreateEvaluating (IObjectValueUpdater updater, ObjectPath path, ObjectValueFlags flags) + { + var val = Create (null, path, null); + val.flags = flags | ObjectValueFlags.Evaluating; + val.updater = updater; + return val; + } + + public static ObjectValue CreateShowMore () + { + return new ObjectValue () { + flags = ObjectValueFlags.IEnumerable, + name = "" + }; + } + + public DebuggerSession DebuggerSession { + get { + return parentFrame?.DebuggerSession; + } + } + + /// + /// Gets the flags of the value + /// + public ObjectValueFlags Flags { + get { return flags; } + } + + /// + /// Name of the value (for example, the property name) + /// + public string Name { + get { return name ?? path[path.Length - 1]; } + set { name = value; } + } + + /// + /// Gets or sets the value of the object + /// + /// + /// The value. + /// + /// + /// Is thrown when trying to set a value on a read-only ObjectValue + /// + /// + /// This value is a string representation of the ObjectValue. The content depends on several evaluation + /// options. For example, if ToString calls are enabled, this value will be the result of calling + /// ToString. + /// If the object is a primitive type, in general the Value will be an expression that represents the + /// value in the target language. For example, when debugging C#, if the property is an string, the value + /// will include the quotation marks and chars like '\' will be properly escaped. + /// If you need to get the real CLR value of the object, use GetRawValue. + /// + public virtual string Value { + get { + return value; + } + set { + if (IsReadOnly || source == null) + throw new InvalidOperationException ("Value is not editable"); + + var res = source.SetValue (path, value, null); + if (res != null) { + this.value = res.Value; + displayValue = res.DisplayValue; + isNull = value == null; + } + } + } + + /// + /// Gets or sets the display value of this object + /// + /// + /// This method returns a string to be used when showing the value of this object. + /// In most cases, the Value and DisplayValue properties return the same text, but there are some cases + /// in which DisplayValue may return a more convenient textual representation of the value, which + /// may not be a valid target language expression. + /// For example in C#, an enum Value includes the full enum type name (e.g. "Gtk.ResponseType.OK"), + /// while DisplayValue only has the enum value name ("OK"). + /// + public string DisplayValue { + get { return displayValue ?? Value; } + set { displayValue = value; } + } + + /// + /// Sets the value of this object, using the default evaluation options + /// + public void SetValue (string value) + { + SetValue (value, parentFrame.DebuggerSession.EvaluationOptions); + } + + /// + /// Sets the value of this object, using the specified evaluation options + /// + /// + /// The value + /// + /// + /// The options + /// + /// + /// Is thrown if the value is read-only + /// + public void SetValue (string value, EvaluationOptions options) + { + if (IsReadOnly || source == null) + throw new InvalidOperationException ("Value is not editable"); + + var res = source.SetValue (path, value, options); + if (res != null) { + this.value = res.Value; + displayValue = res.DisplayValue; + } + } + + /// + /// Gets the raw value of this object + /// + /// + /// The raw value. + /// + /// + /// This method can be used to get the CLR value of the object. For example, if this ObjectValue is + /// a property of type String, this method will return the System.String value of the property. + /// If this ObjectValue refers to an object instead of a primitive value, then a RawValue object + /// will be returned. RawValue can be used to get and set members of an object, and to call methods. + /// If this ObjectValue refers to an array, then a RawValueArray object will be returned. + /// + public object GetRawValue () + { + var ops = parentFrame.DebuggerSession.EvaluationOptions.Clone (); + ops.EllipsizeStrings = false; + + return GetRawValue (ops); + } + + /// + /// Gets the raw value of this object + /// + /// + /// The evaluation options + /// + /// + /// The raw value. + /// + /// + /// This method can be used to get the CLR value of the object. For example, if this ObjectValue is + /// a property of type String, this method will return the System.String value of the property. + /// If this ObjectValue refers to an object instead of a primitive value, then a RawValue object + /// will be returned. RawValue can be used to get and set members of an object, and to call methods. + /// If this ObjectValue refers to an array, then a RawValueArray object will be returned. + /// + public object GetRawValue (EvaluationOptions options) + { + if (source == null && (IsEvaluating || IsEvaluatingGroup)) { + if (!WaitHandle.WaitOne (options.EvaluationTimeout)) { + throw new Evaluation.TimeOutException (); + } + } + + var res = source.GetRawValue (path, options); + if (res is IRawObject raw) + raw.Connect (parentFrame.DebuggerSession, options); + + return res; + } + + /// + /// Sets the raw value of this object + /// + /// + /// The value + /// + /// + /// The provided value can be a primitive type, a RawValue object or a RawValueArray object. + /// + public void SetRawValue (object value) + { + SetRawValue (value, parentFrame.DebuggerSession.EvaluationOptions); + } + + /// + /// Sets the raw value of this object + /// + /// + /// The value + /// + /// + /// The evaluation options + /// + /// + /// The provided value can be a primitive type, a RawValue object or a RawValueArray object. + /// + public void SetRawValue (object value, EvaluationOptions options) + { + if (source == null && (IsEvaluating || IsEvaluatingGroup)) { + if (!WaitHandle.WaitOne (options.EvaluationTimeout)) { + throw new Evaluation.TimeOutException (); + } + } + source.SetRawValue (path, value, options); + } + + /// + /// Full name of the type of the object + /// + public string TypeName { + get { return typeName; } + set { typeName = value; } + } + + /// + /// Gets or sets the child selector. + /// + /// + /// The child selector is an expression which can be concatenated to a parent expression to get this child. + /// For example, if this object is a reference to a field named 'foo' of an object, the child + /// selector is '.foo'. + /// + public string ChildSelector { + get { + if (childSelector != null) + return childSelector; + + if ((flags & ObjectValueFlags.ArrayElement) != 0) + return Name; + + return "." + Name; + } + set { childSelector = value; } + } + + /// + /// Gets a value indicating whether this object has children. + /// + /// + /// true if this instance has children; otherwise, false. + /// + public bool HasChildren { + get { + if (isNull) + return false; + if (IsEvaluating) + return false; + if (children != null) + return children.Count > 0; + if (source == null) + return false; + if (IsArray) + return arrayCount > 0; + if (IsObject) + return true; + return false; + } + } + + /// + /// Gets a child value + /// + /// + /// The child. + /// + /// + /// Name of the member + /// + /// + /// This method can be used to get a member of an object (such as a field or property) + /// + public ObjectValue GetChild (string name) + { + return GetChild (name, parentFrame?.DebuggerSession.EvaluationOptions); + } + + /// + /// Gets a child value + /// + /// + /// The child. + /// + /// + /// Name of the member + /// + /// + /// Options to be used to evaluate the child + /// + /// + /// This method can be used to get a member of an object (such as a field or property) + /// + public ObjectValue GetChild (string name, EvaluationOptions options) + { + if (IsArray) + throw new InvalidOperationException ("Object is an array."); + if (IsEvaluating) + return null; + + if (children == null) { + children = new List (); + if (source != null) { + try { + var cs = source.GetChildren (path, -1, -1, options); + ConnectCallbacks (parentFrame, cs); + children.AddRange (cs); + } catch (Exception ex) { + children = null; + return CreateFatalError ("", ex.Message, ObjectValueFlags.ReadOnly); + } + } + } + + foreach (var child in children) { + if (child.Name == name) + return child; + } + + return null; + } + + /// + /// Gets all children of the object + /// + /// + /// An array of all child values + /// + public ObjectValue[] GetAllChildren () + { + return GetAllChildren (parentFrame.DebuggerSession.EvaluationOptions); + } + + /// + /// Gets all children of the object + /// + /// + /// An array of all child values + /// + /// + /// Options to be used to evaluate the children + /// + public ObjectValue[] GetAllChildren (EvaluationOptions options) + { + if (IsEvaluating) + return new ObjectValue[0]; + + if (IsArray) { + GetArrayItem (arrayCount - 1); + return children.ToArray (); + } + + if (children == null) { + children = new List (); + if (source != null) { + try { + var cs = source.GetChildren (path, -1, -1, options); + ConnectCallbacks (parentFrame, cs); + children.AddRange (cs); + } catch (Exception ex) { + if (parentFrame != null) + parentFrame.DebuggerSession.OnDebuggerOutput (true, ex.ToString ()); + children.Add (CreateFatalError ("", ex.Message, ObjectValueFlags.ReadOnly)); + } + } + } + + return children.ToArray (); + } + + public ObjectValue[] GetRangeOfChildren (int index, int count) + { + return GetRangeOfChildren (index, count, parentFrame.DebuggerSession.EvaluationOptions); + } + + public ObjectValue[] GetRangeOfChildren (int index, int count, EvaluationOptions options) + { + if (IsEvaluating) + return new ObjectValue[0]; + + if (IsArray) { + GetArrayItem (arrayCount - 1); + if (index >= ArrayCount) + return new ObjectValue[0]; + return children.Skip (index).Take (Math.Min (count, ArrayCount - index)).ToArray (); + } + + if (children == null) { + children = new List (); + } + if (children.Count < index + count) { + if (source != null) { + try { + ObjectValue[] cs = source.GetChildren (path, children.Count, index + count, options); + ConnectCallbacks (parentFrame, cs); + children.AddRange (cs); + } catch (Exception ex) { + if (parentFrame != null) + parentFrame.DebuggerSession.OnDebuggerOutput (true, ex.ToString ()); + children.Add (CreateFatalError ("", ex.Message, ObjectValueFlags.ReadOnly)); + } + } + } + + if (index >= children.Count) + return new ObjectValue[0]; + + return children.Skip (index).Take (Math.Min (count, children.Count - index)).ToArray (); + } + + /// + /// Gets an item of an array + /// + /// + /// The array item. + /// + /// + /// Item index + /// + /// + /// Is thrown if this object is not an array (IsArray returns false) + /// + public ObjectValue GetArrayItem (int index) + { + return GetArrayItem (index, parentFrame.DebuggerSession.EvaluationOptions); + } + + /// + /// Gets an item of an array + /// + /// + /// The array item. + /// + /// + /// Item index + /// + /// + /// Options to be used to evaluate the item + /// + /// + /// Is thrown if this object is not an array (IsArray returns false) + /// + public ObjectValue GetArrayItem (int index, EvaluationOptions options) + { + if (!IsArray) + throw new InvalidOperationException ("Object is not an array."); + + if (index >= arrayCount || index < 0 || IsEvaluating) + throw new IndexOutOfRangeException (); + + if (children == null) + children = new List (); + + if (index >= children.Count) { + int nc = (index + 50); + + if (nc > arrayCount) + nc = arrayCount; + + nc = nc - children.Count; + + try { + var items = source.GetChildren (path, children.Count, nc, options); + ConnectCallbacks (parentFrame, items); + children.AddRange (items); + } catch (Exception ex) { + return CreateFatalError ("", ex.Message, ObjectValueFlags.ArrayElement | ObjectValueFlags.ReadOnly); + } + } + + return children [index]; + } + + /// + /// Gets the number of items of an array + /// + /// + /// Is thrown if this object is not an array (IsArray returns false) + /// + public int ArrayCount { + get { + if (!IsArray) + throw new InvalidOperationException ("Object is not an array."); + + if (IsEvaluating) + return 0; + + return arrayCount; + } + } + + public bool IsNull { + get { return isNull; } + } + + public bool IsReadOnly { + get { return HasFlag (ObjectValueFlags.ReadOnly); } + } + + public bool IsArray { + get { return HasFlag (ObjectValueFlags.Array); } + } + + public bool IsObject { + get { return HasFlag (ObjectValueFlags.Object); } + } + + public bool IsPrimitive { + get { return HasFlag (ObjectValueFlags.Primitive); } + } + + public bool IsUnknown { + get { return HasFlag (ObjectValueFlags.Unknown); } + } + + public bool IsNotSupported { + get { return HasFlag (ObjectValueFlags.NotSupported); } + } + + public bool IsImplicitNotSupported { + get { return HasFlag (ObjectValueFlags.ImplicitNotSupported); } + } + + public bool IsError { + get { return HasFlag (ObjectValueFlags.Error); } + } + + public bool IsEvaluating { + get { return HasFlag (ObjectValueFlags.Evaluating); } + } + + public bool IsEvaluatingGroup { + get { return HasFlag (ObjectValueFlags.EvaluatingGroup); } + } + + public bool CanRefresh { + get { return source != null && !HasFlag (ObjectValueFlags.NoRefresh); } + } + + public bool HasFlag (ObjectValueFlags flag) + { + return (flags & flag) != 0; + } + + public event EventHandler ValueChanged { + add { + lock (mutex) { + valueChanged += value; + if (!IsEvaluating) + value (this, EventArgs.Empty); + } + } + remove { + lock (mutex) { + valueChanged -= value; + } + } + } + + /// + /// Refreshes the value of this object + /// + /// + /// This method can be called to get a more up-to-date value for this object. + /// + public void Refresh () + { + Refresh (parentFrame.DebuggerSession.EvaluationOptions); + } + + /// + /// Refreshes the value of this object + /// + /// + /// This method can be called to get a more up-to-date value for this object. + /// + public void Refresh (EvaluationOptions options) + { + if (!CanRefresh) + return; + + var val = source.GetValue (path, options); + UpdateFrom (val, true); + } + + /// + /// Gets a wait handle which can be used to wait for the evaluation of this object to end + /// + /// + /// The wait handle. + /// + public WaitHandle WaitHandle { + get { + lock (mutex) { + if (evaluatedEvent == null) + evaluatedEvent = new ManualResetEvent (!IsEvaluating); + return evaluatedEvent; + } + } + } + + internal IObjectValueUpdater Updater { + get { return updater; } + } + + internal void UpdateFrom (ObjectValue val, bool notify) + { + lock (mutex) { + arrayCount = val.arrayCount; + if (val.name != null) + name = val.name; + value = val.value; + displayValue = val.displayValue; + typeName = val.typeName; + flags = val.flags; + source = val.source; + children = val.children; + path = val.path; + updater = val.updater; + ConnectCallbacks (parentFrame, this); + if (evaluatedEvent != null) + evaluatedEvent.Set (); + if (notify && valueChanged != null) + valueChanged (this, EventArgs.Empty); + } + } + + internal UpdateCallback GetUpdateCallback () + { + if (IsEvaluating) { + if (updateCallback == null) + updateCallback = new UpdateCallback (new UpdateCallbackProxy (this), path); + return updateCallback; + } + + return null; + } + + ~ObjectValue () + { +#if !NETCOREAPP + if (updateCallback != null) + System.Runtime.Remoting.RemotingServices.Disconnect ((UpdateCallbackProxy)updateCallback.Callback); +#endif + } + + internal static void ConnectCallbacks (StackFrame parentFrame, params ObjectValue[] values) + { + Dictionary> callbacks = null; + var valueList = new List (values); + + for (int n = 0; n < valueList.Count; n++) { + var val = valueList [n]; + val.source = parentFrame.DebuggerSession.WrapDebuggerObject (val.source); + val.updater = parentFrame.DebuggerSession.WrapDebuggerObject (val.updater); + val.parentFrame = parentFrame; + + var cb = val.GetUpdateCallback (); + if (cb != null) { + if (callbacks == null) + callbacks = new Dictionary> (); + + List list; + if (!callbacks.TryGetValue (val.Updater, out list)) { + list = new List (); + callbacks [val.Updater] = list; + } + + list.Add (cb); + } + + if (val.children != null) + valueList.AddRange (val.children); + } + + if (callbacks != null) { + // Do the callback connection in a background thread + ThreadPool.QueueUserWorkItem (delegate { + foreach (KeyValuePair> cbs in callbacks) { + cbs.Key.RegisterUpdateCallbacks (cbs.Value.ToArray ()); + } + }); + } + } + } + + class UpdateCallbackProxy: MarshalByRefObject, IObjectValueUpdateCallback + { + readonly WeakReference valRef; + + public void UpdateValue (ObjectValue newValue) + { + var val = valRef.Target as ObjectValue; + + if (val != null) + val.UpdateFrom (newValue, true); + } + + public UpdateCallbackProxy (ObjectValue val) + { + valRef = new WeakReference (val); + } + } +} diff --git a/VisualPascalABCNETLinux/MonoDebugging/Client/ObjectValueFlags.cs b/VisualPascalABCNETLinux/MonoDebugging/Client/ObjectValueFlags.cs new file mode 100644 index 000000000..8fdff013e --- /dev/null +++ b/VisualPascalABCNETLinux/MonoDebugging/Client/ObjectValueFlags.cs @@ -0,0 +1,73 @@ +// ObjectValueKind.cs +// +// Author: +// Lluis Sanchez Gual +// +// Copyright (c) 2008 Novell, Inc (http://www.novell.com) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// +// + +using System; + +namespace Mono.Debugging.Client +{ + [Flags] + public enum ObjectValueFlags: uint { + None = 0, + Object = 1, // The value is an object + Array = 1 << 1, // The value is an array + Primitive = 1 << 2, // The value is a primitive value + Unknown = 1 << 3, // The evaluated identifier is unknown + Error = 1 << 4, // The expression evaluation returned an error + NotSupported = 1 << 5, // The expression is valid but its evaluation is not supported + Evaluating = 1 << 6, // The expression is being evaluated. The value will be updated when done. + ImplicitNotSupported = 1 << 7, // The expression is valid but it can't be performed implicitly. + KindMask = 0x000000ff, + + Field = 1 << 8, + Property = 1 << 9, + Parameter = 1 << 10, + Variable = 1 << 11, + ArrayElement = 1 << 12, + Method = 1 << 13, + Literal = 1 << 14, + Type = 1 << 15, + Namespace = 1 << 16, + Group = 1 << 17, + OriginMask = 0x0003ff00, + + Global = 1 << 18, // For fields, it means static + ReadOnly = 1 << 19, + NoRefresh = 1 << 20, // When set, this value can't be refreshed + EvaluatingGroup = 1 << 21, // When set, this value represents a set of values being evaluated + // When evaluation ends, the value is updated, and the children are the + // values represented by this group + IEnumerable = 1 << 22, + + // For field and property + Public = 1 << 24, + Protected = 1 << 25, + Internal = 1 << 26, + Private = 1 << 27, + InternalProtected = Internal | Protected, + AccessMask = Public | Protected | Internal | Private + } +} diff --git a/VisualPascalABCNETLinux/MonoDebugging/Client/OutputOptions.cs b/VisualPascalABCNETLinux/MonoDebugging/Client/OutputOptions.cs new file mode 100644 index 000000000..5481137ae --- /dev/null +++ b/VisualPascalABCNETLinux/MonoDebugging/Client/OutputOptions.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Mono.Debugging.Client +{ + [Serializable] + public class OutputOptions + { + public bool ModuleLoaded { get; set; } + public bool ModuleUnoaded { get; set; } + public bool ExceptionMessage { get; set; } + public bool SymbolSearch { get; set; } + public bool ThreadExited { get; set; } + public bool ProcessExited { get; set; } + + } +} diff --git a/VisualPascalABCNETLinux/MonoDebugging/Client/ProcessEventArgs.cs b/VisualPascalABCNETLinux/MonoDebugging/Client/ProcessEventArgs.cs new file mode 100644 index 000000000..f021daeaa --- /dev/null +++ b/VisualPascalABCNETLinux/MonoDebugging/Client/ProcessEventArgs.cs @@ -0,0 +1,43 @@ +// ProcessEventArgs.cs +// +// Author: +// Lluis Sanchez Gual +// +// Copyright (c) 2008 Novell, Inc (http://www.novell.com) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// +// + +using System; + +namespace Mono.Debugging.Client +{ + public class ProcessEventArgs : EventArgs + { + public ProcessEventArgs (int processId) + { + ProcessId = processId; + } + + public int ProcessId { + get; private set; + } + } +} diff --git a/VisualPascalABCNETLinux/MonoDebugging/Client/ProcessInfo.cs b/VisualPascalABCNETLinux/MonoDebugging/Client/ProcessInfo.cs new file mode 100644 index 000000000..5ba023aa8 --- /dev/null +++ b/VisualPascalABCNETLinux/MonoDebugging/Client/ProcessInfo.cs @@ -0,0 +1,90 @@ +// ProcessInfo.cs +// +// Author: +// Lluis Sanchez Gual +// +// Copyright (c) 2008 Novell, Inc (http://www.novell.com) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// +// + +using System; + +namespace Mono.Debugging.Client +{ + [Serializable] + public class ProcessInfo + { + long id; + string name; + string description; + + [NonSerialized] + DebuggerSession session; + + internal void Attach (DebuggerSession session) + { + this.session = session; + } + + public long Id { + get { + return id; + } + } + + public string Name { + get { + return name; + } + } + + public string Description { + get { + return description; + } + } + + public ProcessInfo (string name, string description) + { + this.name = name; + this.description = description; + } + + public ProcessInfo (long id, string name) + { + this.id = id; + this.name = name; + } + + public ThreadInfo[] GetThreads () + { + return session.GetThreads (id); + } + + /// + /// Gets assemblies from the debugger session that matches the process ID. + /// + public Assembly[] GetAssemblies () + { + return session.GetAssemblies (id); + } + } +} diff --git a/VisualPascalABCNETLinux/MonoDebugging/Client/RawValue.cs b/VisualPascalABCNETLinux/MonoDebugging/Client/RawValue.cs new file mode 100644 index 000000000..8b99c1131 --- /dev/null +++ b/VisualPascalABCNETLinux/MonoDebugging/Client/RawValue.cs @@ -0,0 +1,311 @@ +// +// RawValue.cs +// +// Author: +// Lluis Sanchez Gual +// +// Copyright (c) 2010 Novell, Inc (http://www.novell.com) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +using System; +using Mono.Debugging.Backend; + +namespace Mono.Debugging.Client +{ + /// + /// Represents an object in the process being debugged + /// + [Serializable] + public class RawValue : IRawObject + { + IRawValue source; + EvaluationOptions options; + DebuggerSession session; + + /// + /// Initializes a new instance of the class. + /// + /// + /// Value source + /// + public RawValue (IRawValue source) + { + this.source = source; + } + + void IRawObject.Connect (DebuggerSession session, EvaluationOptions options) + { + this.options = options; + this.session = session; + source = session.WrapDebuggerObject (source); + } + + internal IRawValue Source { + get { return this.source; } + } + + /// + /// Full name of the type of the object + /// + public string TypeName { get; set; } + + /// + /// Invokes a method on the object + /// + /// + /// The result of the invocation + /// + /// + /// The name of the method + /// + /// + /// The parameters (primitive type values, RawValue instances or RawValueArray instances) + /// + public object CallMethod (string methodName, params object [] parameters) + { + object res = source.CallMethod (methodName, parameters, options); + RawValue val = res as RawValue; + if (val != null) + val.options = options; + IRawObject raw = res as IRawObject; + if (raw != null) + raw.Connect (session, options); + return res; + } + + public object CallMethod (string methodName, out object [] outArgs, params object [] parameters) + { + object res = source.CallMethod (methodName, parameters, out outArgs, options); + RawValue val = res as RawValue; + if (val != null) + val.options = options; + IRawObject raw = res as IRawObject; + if (raw != null) + raw.Connect (session, options); + return res; + } + + /// + /// Gets the value of a field or property + /// + /// + /// The value (a primitive type value, a RawValue instance or a RawValueArray instance) + /// + /// + /// Name of the field or property + /// + public object GetMemberValue (string name) + { + object res = source.GetMemberValue (name, options); + RawValue val = res as RawValue; + if (val != null) + val.options = options; + IRawObject raw = res as IRawObject; + if (raw != null) + raw.Connect (session, options); + return res; + } + + /// + /// Sets the value of a field or property + /// + /// + /// Name of the field or property + /// + /// + /// The value (a primitive type value, a RawValue instance or a RawValueArray instance) + /// + public void SetMemberValue (string name, object value) + { + source.SetMemberValue (name, value, options); + } + } + + /// + /// Represents an array of objects in the process being debugged + /// + [Serializable] + public class RawValueArray : IRawObject + { + IRawValueArray source; + EvaluationOptions options; + DebuggerSession session; + int [] dimensions; + + /// + /// Initializes a new instance of the class. + /// + /// + /// Value source. + /// + public RawValueArray (IRawValueArray source) + { + this.source = source; + } + + void IRawObject.Connect (DebuggerSession session, EvaluationOptions options) + { + this.options = options; + this.session = session; + source = session.WrapDebuggerObject (source); + } + + internal IRawValueArray Source { + get { return this.source; } + } + + /// + /// Full type name of the array items + /// + public string ElementTypeName { get; set; } + + /// + /// Gets or sets the item at the specified index. + /// + /// + /// The index + /// + /// + /// The item value can be a primitive type value, a RawValue instance or a RawValueArray instance. + /// + public object this [int index] { + get { + return source.GetValue (new int [] { index }); + } + set { + source.SetValue (new int [] { index }, value); + } + } + + /// + /// Gets the values. + /// + /// The items. + /// The index. + /// The number of items to get. + /// + /// This method is useful for incrementally fetching an array in order to avoid + /// long waiting periods when the array is too large for ToArray(). + /// + public Array GetValues (int index, int count) + { + return source.GetValues (new int [] { index }, count); + } + + /// + /// Returns an array with all items of the RawValueArray + /// + /// + /// This method is useful to avoid unnecessary debugger-debuggee roundtrips + /// when processing all items of an array. For example, if a RawValueArray + /// represents an image encoded in a byte[], getting the values one by one + /// using the indexer is very slow. The ToArray() will return the whole byte[] + /// in a single call. + /// + public Array ToArray () + { + var array = source.ToArray (); + for (int i = 0; i < array.Length; i++) { + var val = array.GetValue (i) as IRawObject; + if (val != null) { + val.Connect (session, options); + } + } + return array; + } + + /// + /// Gets the length of the array + /// + public int Length { + get { + if (dimensions == null) + dimensions = source.Dimensions; + return dimensions [0]; + } + } + } + + /// + /// Represents a string object in the process being debugged + /// + [Serializable] + public class RawValueString : IRawObject + { + IRawValueString source; + + /// + /// Initializes a new instance of the class. + /// + /// + /// Value source. + /// + public RawValueString (IRawValueString source) + { + this.source = source; + } + + void IRawObject.Connect (DebuggerSession session, EvaluationOptions options) + { + source = session.WrapDebuggerObject (source); + } + + internal IRawValueString Source { + get { return source; } + } + + /// + /// Gets the length of the string + /// + public int Length { + get { return source.Length; } + } + + /// + /// Gets a substring of the string + /// + /// + /// The starting index of the requested substring. + /// + /// + /// The length of the requested substring. + /// + public string Substring (int index, int length) + { + return source.Substring (index, length); + } + + /// + /// Gets the value. + /// + /// + /// The value. + /// + public string Value { + get { return source.Value; } + } + } + + interface IRawObject + { + void Connect (DebuggerSession session, EvaluationOptions options); + } +} + diff --git a/VisualPascalABCNETLinux/MonoDebugging/Client/RunToCursorBreakpoint.cs b/VisualPascalABCNETLinux/MonoDebugging/Client/RunToCursorBreakpoint.cs new file mode 100644 index 000000000..00ed6f0f4 --- /dev/null +++ b/VisualPascalABCNETLinux/MonoDebugging/Client/RunToCursorBreakpoint.cs @@ -0,0 +1,34 @@ +// +// RunToCursorBreakpoint.cs +// +// Author: Jeffrey Stedfast +// +// Copyright (c) 2014 Xamarin Inc. (www.xamarin.com) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +namespace Mono.Debugging.Client +{ + public sealed class RunToCursorBreakpoint : Breakpoint + { + public RunToCursorBreakpoint (string fileName, int line, int column) : base (fileName, line, column) + { + } + } +} diff --git a/VisualPascalABCNETLinux/MonoDebugging/Client/SourceLink.cs b/VisualPascalABCNETLinux/MonoDebugging/Client/SourceLink.cs new file mode 100644 index 000000000..56cde159e --- /dev/null +++ b/VisualPascalABCNETLinux/MonoDebugging/Client/SourceLink.cs @@ -0,0 +1,25 @@ +using System; +using System.IO; + +namespace Mono.Debugging.Client +{ + [Serializable] + public class SourceLink + { + public string Uri { get; } + + public string RelativeFilePath { get; } + + public SourceLink (string uri, string relativeFilePath) + { + RelativeFilePath = relativeFilePath; + Uri = uri; + } + + public string GetDownloadLocation (string cachePath) + { + var uri = new Uri (Uri); + return Path.Combine (cachePath, uri.Host + uri.PathAndQuery); + } + } +} diff --git a/VisualPascalABCNETLinux/MonoDebugging/Client/SourceLocation.cs b/VisualPascalABCNETLinux/MonoDebugging/Client/SourceLocation.cs new file mode 100644 index 000000000..f156f70df --- /dev/null +++ b/VisualPascalABCNETLinux/MonoDebugging/Client/SourceLocation.cs @@ -0,0 +1,190 @@ +using System; +using System.IO; +using System.Collections.Generic; +using System.Security.Cryptography; + +namespace Mono.Debugging.Client +{ + [Serializable] + public class SourceLocation + { + public string MethodName { get; private set; } + public string FileName { get; private set; } + public int Line { get; private set; } + public int Column { get; private set; } + public int EndLine { get; private set; } + public int EndColumn { get; private set; } + public byte[] FileHash { get; private set; } + public SourceLink SourceLink { get; private set; } + + [Obsolete] + public SourceLocation (string methodName, string fileName, int line) + : this (methodName, fileName, line, -1, -1, -1, null) + { + } + + public SourceLocation (string methodName, string fileName, int line, int column, int endLine, int endColumn) + : this (methodName, fileName, line, column, endLine, endColumn, null, null) + { + + } + public SourceLocation (string methodName, string fileName, int line, int column, int endLine, int endColumn, byte [] hash = null) + : this (methodName, fileName, line, column, endLine, endColumn, hash, null) + { + } + + public SourceLocation (string methodName, string fileName, int line, int column, int endLine, int endColumn, byte[] hash = null, SourceLink sourceLink = null) + { + this.MethodName = methodName; + this.FileName = fileName; + this.Line = line; + this.Column = column; + this.EndLine = endLine; + this.EndColumn = endColumn; + this.FileHash = hash; + this.SourceLink = sourceLink; + } + + public override string ToString () + { + return string.Format("[SourceLocation Method={0}, Filename={1}, Line={2}, Column={3}]", MethodName, FileName, Line, Column); + } + + static void ComputeHashes (Stream stream, HashAlgorithm hash, HashAlgorithm dos, HashAlgorithm unix) + { + var unixBuffer = new byte[4096 + 1]; + var dosBuffer = new byte[8192 + 1]; + var buffer = new byte[4096]; + byte pc = 0; + int count; + + try { + while ((count = stream.Read (buffer, 0, buffer.Length)) > 0) { + int unixIndex = 0, dosIndex = 0; + + for (int i = 0; i < count; i++) { + var c = buffer[i]; + + if (c == (byte) '\r') { + if (pc == (byte) '\r') + unixBuffer[unixIndex++] = pc; + dosBuffer[dosIndex++] = c; + } else if (c == (byte) '\n') { + if (pc != (byte) '\r') + dosBuffer[dosIndex++] = (byte) '\r'; + unixBuffer[unixIndex++] = c; + dosBuffer[dosIndex++] = c; + } else { + if (pc == (byte) '\r') + unixBuffer[unixIndex++] = pc; + unixBuffer[unixIndex++] = c; + dosBuffer[dosIndex++] = c; + } + + pc = c; + } + + hash.TransformBlock (buffer, 0, count, outputBuffer: null, outputOffset: 0); + dos.TransformBlock (dosBuffer, 0, dosIndex, outputBuffer: null, outputOffset: 0); + unix.TransformBlock (unixBuffer, 0, unixIndex, outputBuffer: null, outputOffset: 0); + } + + hash.TransformFinalBlock (buffer, 0, 0); + dos.TransformFinalBlock (buffer, 0, 0); + unix.TransformFinalBlock (buffer, 0, 0); + } finally { + + } + } + + public static List ComputeChecksums (string path, string algorithm) + { + using (var stream = File.OpenRead (path)) { + using (var hash = HashAlgorithm.Create (algorithm)) { + using (var dos = HashAlgorithm.Create (algorithm)) { + using (var unix = HashAlgorithm.Create (algorithm)) { + ComputeHashes (stream, hash, dos, unix); + + var checksums = new List (3); + checksums.Add (hash.Hash); + checksums.Add (dos.Hash); + checksums.Add (unix.Hash); + return checksums; + } + } + } + } + } + + static bool ChecksumsEqual (byte[] calculated, byte[] checksum, int skip = 0) + { + if (skip > 0) { + if (calculated.Length < checksum.Length - skip) + return false; + } else { + if (calculated.Length != checksum.Length) + return false; + } + + for (int i = 0, csi = skip; csi < checksum.Length; i++, csi++) { + if (calculated[i] != checksum[csi]) + return false; + } + + return true; + } + + static bool CheckHash (Stream stream, string algorithm, byte[] checksum) + { + using (var hash = HashAlgorithm.Create (algorithm)) { + int size = hash.HashSize / 8; + + using (var dos = HashAlgorithm.Create (algorithm)) { + using (var unix = HashAlgorithm.Create (algorithm)) { + stream.Position = 0; + + ComputeHashes (stream, hash, dos, unix); + + if (checksum [0] == size && checksum.Length < size) { + return ChecksumsEqual (hash.Hash, checksum, 1) || + ChecksumsEqual (unix.Hash, checksum, 1) || + ChecksumsEqual (dos.Hash, checksum, 1); + } + + return ChecksumsEqual (hash.Hash, checksum) || + ChecksumsEqual (unix.Hash, checksum) || + ChecksumsEqual (dos.Hash, checksum); + } + } + } + } + + public static bool CheckFileHash (string path, byte[] checksum) + { + if (checksum == null || checksum.Length == 0 || !File.Exists (path)) + return false; + + using (var stream = File.OpenRead (path)) { + if (checksum.Length == 16) { + // Note: Roslyn SHA1 hashes are 16 bytes and start w/ 20 + if (checksum[0] == 20 && CheckHash (stream, "SHA1", checksum)) + return true; + + // Note: Roslyn SHA256 hashes are 16 bytes and start w/ 32 + if (checksum[0] == 32 && CheckHash (stream, "SHA256", checksum)) + return true; + + return CheckHash (stream, "MD5", checksum); + } + + if (checksum.Length == 20) + return CheckHash (stream, "SHA1", checksum); + + if (checksum.Length == 32) + return CheckHash (stream, "SHA256", checksum); + } + + return false; + } + } +} diff --git a/VisualPascalABCNETLinux/MonoDebugging/Client/StackFrame.cs b/VisualPascalABCNETLinux/MonoDebugging/Client/StackFrame.cs new file mode 100644 index 000000000..88c23f487 --- /dev/null +++ b/VisualPascalABCNETLinux/MonoDebugging/Client/StackFrame.cs @@ -0,0 +1,475 @@ +using System; +using System.IO; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +using Mono.Debugging.Backend; + +namespace Mono.Debugging.Client +{ + [Serializable] + public class StackFrame + { + long address; + string addressSpace; + SourceLocation location; + IBacktrace sourceBacktrace; + string language; + int index; + bool isExternalCode; + bool isDebuggerHidden; + bool hasDebugInfo; + string fullTypeName; + + [NonSerialized] + DebuggerSession session; + [NonSerialized] + bool haveParameterValues; + [NonSerialized] + ObjectValue[] parameters; + + public StackFrame (long address, string addressSpace, SourceLocation location, string language, bool isExternalCode, bool hasDebugInfo, bool isDebuggerHidden, string fullModuleName, string fullTypeName) + { + this.address = address; + this.addressSpace = addressSpace; + this.location = location; + this.language = language; + this.isExternalCode = isExternalCode; + this.isDebuggerHidden = isDebuggerHidden; + this.hasDebugInfo = hasDebugInfo; + this.FullModuleName = fullModuleName; + this.fullTypeName = fullTypeName; + } + + public StackFrame (long address, string addressSpace, SourceLocation location, string language, bool isExternalCode, bool hasDebugInfo, string fullModuleName, string fullTypeName) + : this (address, addressSpace, location, language, isExternalCode, hasDebugInfo, false, fullModuleName, fullTypeName) + { + } + + [Obsolete] + public StackFrame (long address, string addressSpace, SourceLocation location, string language) + : this (address, addressSpace, location, language, string.IsNullOrEmpty (location.FileName), true, "", "") + { + } + + [Obsolete] + public StackFrame (long address, string addressSpace, string module, string method, string filename, int line, string language) + : this (address, addressSpace, new SourceLocation (method, filename, line), language) + { + } + + public StackFrame (long address, SourceLocation location, string language, bool isExternalCode, bool hasDebugInfo) + : this (address, "", location, language, string.IsNullOrEmpty (location.FileName) || isExternalCode, hasDebugInfo, "", "") + { + } + + public StackFrame (long address, SourceLocation location, string language) + : this (address, "", location, language, string.IsNullOrEmpty (location.FileName), true, "", "") + { + } + + internal void Attach (DebuggerSession debugSession) + { + session = debugSession; + } + + public DebuggerSession DebuggerSession { + get { return session; } + } + + public SourceLocation SourceLocation { + get { return location; } + } + + public long Address { + get { return address; } + } + + public string AddressSpace { + get { return addressSpace; } + } + + internal IBacktrace SourceBacktrace { + get { return sourceBacktrace; } + set { sourceBacktrace = value; } + } + + public int Index { + get { return index; } + internal set { index = value; } + } + + public string Language { + get { return language; } + } + + public bool IsExternalCode { + get { return isExternalCode; } + } + + public bool IsDebuggerHidden { + get { return isDebuggerHidden; } + } + + public bool HasDebugInfo { + get { return hasDebugInfo; } + } + + public string FullModuleName { get; protected set; } + + public string FullTypeName { + get { return fullTypeName; } + } + + /// + /// Gets the full name of the stackframe. Which respects Session.EvaluationOptions.StackFrameFormat + /// + [Obsolete] + public string FullStackframeText { + get { return GetFullStackFrameText (); } + } + + /// + /// Used to ignore a first-chance exception in the given location (module, type, method and IL offset). + /// + public string GetLocationSignature () + { + string methodName = this.SourceLocation.MethodName; + if (methodName != null && !methodName.Contains("!")) { + methodName = $"{Path.GetFileName (this.FullModuleName)}!{methodName}"; + } + + if (this.Address != 0) { + methodName = $"{methodName}:{Address}"; + } + + return methodName; + } + + public string GetFullStackFrameText () + { + return GetFullStackFrameText (session.EvaluationOptions); + } + + public virtual string GetFullStackFrameText (EvaluationOptions options) + { + using (var cts = new CancellationTokenSource (options.MemberEvaluationTimeout)) + return GetFullStackFrameTextAsync (options, false, cts.Token).GetAwaiter ().GetResult (); + } + + public Task GetFullStackFrameTextAsync (CancellationToken cancellationToken = default (CancellationToken)) + { + return GetFullStackFrameTextAsync (session.EvaluationOptions, true, cancellationToken); + } + + public virtual Task GetFullStackFrameTextAsync (EvaluationOptions options, CancellationToken cancellationToken = default (CancellationToken)) + { + return GetFullStackFrameTextAsync (options, true, cancellationToken); + } + + async Task GetFullStackFrameTextAsync (EvaluationOptions options, bool doAsync, CancellationToken cancellationToken) + { + // If MethodName starts with "[", then it's something like [ExternalCode] + if (SourceLocation.MethodName.StartsWith ("[", StringComparison.Ordinal)) + return SourceLocation.MethodName; + + options = options.Clone (); + if (options.StackFrameFormat.ParameterValues) { + options.AllowMethodEvaluation = true; + options.AllowToStringCalls = true; + options.AllowTargetInvoke = true; + } else { + options.AllowMethodEvaluation = false; + options.AllowToStringCalls = false; + options.AllowTargetInvoke = false; + } + + // Cache the method parameters. Only refresh the method params iff the cached args do not + // already have parameter values. Once we have parameter values, we never have to + // refresh the cached parameters because we can just omit the parameter values when + // constructing the display string. + if (parameters == null || (options.StackFrameFormat.ParameterValues && !haveParameterValues)) { + haveParameterValues = options.StackFrameFormat.ParameterValues; + parameters = GetParameters (options); + } + + var methodNameBuilder = new StringBuilder (); + + if (options.StackFrameFormat.Module && !string.IsNullOrEmpty (FullModuleName)) { + methodNameBuilder.Append (Path.GetFileName (FullModuleName)); + methodNameBuilder.Append ('!'); + } + + methodNameBuilder.Append (SourceLocation.MethodName); + + if (options.StackFrameFormat.ParameterTypes || options.StackFrameFormat.ParameterNames || options.StackFrameFormat.ParameterValues) { + methodNameBuilder.Append ('('); + for (int n = 0; n < parameters.Length; n++) { + if (parameters[n].IsEvaluating) { + var tcs = new TaskCompletionSource (); + EventHandler updated = (s, e) => { + tcs.TrySetResult (true); + }; + parameters[n].ValueChanged += updated; + try { + using (var registration = cancellationToken.Register (() => tcs.TrySetCanceled ())) { + if (parameters[n].IsEvaluating) { + if (doAsync) { + await tcs.Task.ConfigureAwait (false); + } else { + tcs.Task.Wait (cancellationToken); + } + } + } + } finally { + parameters[n].ValueChanged -= updated; + } + } + if (n > 0) + methodNameBuilder.Append (", "); + if (options.StackFrameFormat.ParameterTypes) { + methodNameBuilder.Append (parameters[n].TypeName); + if (options.StackFrameFormat.ParameterNames) + methodNameBuilder.Append (' '); + } + if (options.StackFrameFormat.ParameterNames) + methodNameBuilder.Append (parameters[n].Name); + if (options.StackFrameFormat.ParameterValues) { + if (options.StackFrameFormat.ParameterTypes || options.StackFrameFormat.ParameterNames) + methodNameBuilder.Append (" = "); + var val = parameters[n].Value ?? string.Empty; + methodNameBuilder.Append (val.Replace ("\r\n", " ").Replace ("\n", " ")); + } + } + methodNameBuilder.Append (')'); + } + + return methodNameBuilder.ToString (); + } + + public ObjectValue[] GetLocalVariables () + { + return GetLocalVariables (session.EvaluationOptions); + } + + public ObjectValue[] GetLocalVariables (EvaluationOptions options) + { + if (!hasDebugInfo) { + DebuggerLoggingService.LogMessage ("Cannot get local variables: no debugging symbols for frame: {0}", this); + return new ObjectValue [0]; + } + + var values = sourceBacktrace.GetLocalVariables (index, options); + ObjectValue.ConnectCallbacks (this, values); + return values; + } + + public ObjectValue[] GetParameters () + { + return GetParameters (session.EvaluationOptions); + } + + public ObjectValue[] GetParameters (EvaluationOptions options) + { + if (!hasDebugInfo) { + DebuggerLoggingService.LogMessage ("Cannot get parameters: no debugging symbols for frame: {0}", this); + return new ObjectValue [0]; + } + + var values = sourceBacktrace.GetParameters (index, options); + ObjectValue.ConnectCallbacks (this, values); + return values; + } + + public ObjectValue[] GetAllLocals () + { + if (!hasDebugInfo) { + DebuggerLoggingService.LogMessage ("Cannot get local variables: no debugging symbols for frame: {0}", this); + return new ObjectValue [0]; + } + + var evaluator = session.FindExpressionEvaluator (this); + + return evaluator != null ? evaluator.GetLocals (this) : GetAllLocals (session.EvaluationOptions); + } + + public ObjectValue[] GetAllLocals (EvaluationOptions options) + { + if (!hasDebugInfo) { + DebuggerLoggingService.LogMessage ("Cannot get local variables: no debugging symbols for frame: {0}", this); + return new ObjectValue [0]; + } + + var values = sourceBacktrace.GetAllLocals (index, options); + ObjectValue.ConnectCallbacks (this, values); + return values; + } + + public ObjectValue GetThisReference () + { + return GetThisReference (session.EvaluationOptions); + } + + public ObjectValue GetThisReference (EvaluationOptions options) + { + if (!hasDebugInfo) { + DebuggerLoggingService.LogMessage ("Cannot get `this' reference: no debugging symbols for frame: {0}", this); + return null; + } + + var value = sourceBacktrace.GetThisReference (index, options); + if (value != null) + ObjectValue.ConnectCallbacks (this, value); + + return value; + } + + public ExceptionInfo GetException () + { + return GetException (session.EvaluationOptions); + } + + public ExceptionInfo GetException (EvaluationOptions options) + { + var value = sourceBacktrace.GetException (index, options); + if (value != null) + value.ConnectCallback (this); + + return value; + } + + public string ResolveExpression (string exp) + { + return session.ResolveExpression (exp, location); + } + + public ObjectValue[] GetExpressionValues (string[] expressions, bool evaluateMethods) + { + var options = session.EvaluationOptions.Clone (); + options.AllowMethodEvaluation = evaluateMethods; + return GetExpressionValues (expressions, options); + } + + public ObjectValue[] GetExpressionValues (string[] expressions, EvaluationOptions options) + { + if (!hasDebugInfo) { + DebuggerLoggingService.LogMessage ("Cannot get expression values: no debugging symbols for frame: {0}", this); + + var vals = new ObjectValue [expressions.Length]; + for (int n = 0; n < expressions.Length; n++) + vals[n] = ObjectValue.CreateUnknown (expressions[n]); + + return vals; + } + + if (options.UseExternalTypeResolver) { + var resolved = new string [expressions.Length]; + for (int n = 0; n < expressions.Length; n++) + resolved[n] = ResolveExpression (expressions[n]); + + expressions = resolved; + } + + var values = sourceBacktrace.GetExpressionValues (index, expressions, options); + ObjectValue.ConnectCallbacks (this, values); + return values; + } + + public ObjectValue GetExpressionValue (string expression, bool evaluateMethods) + { + var options = session.EvaluationOptions.Clone (); + options.AllowMethodEvaluation = evaluateMethods; + return GetExpressionValue (expression, options); + } + + public ObjectValue GetExpressionValue (string expression, EvaluationOptions options) + { + if (!hasDebugInfo) { + DebuggerLoggingService.LogMessage ("Cannot get expression value: no debugging symbols for frame: {0}", this); + return ObjectValue.CreateUnknown (expression); + } + + if (options.UseExternalTypeResolver) + expression = ResolveExpression (expression); + + var values = sourceBacktrace.GetExpressionValues (index, new [] { expression }, options); + ObjectValue.ConnectCallbacks (this, values); + return values [0]; + } + + /// + /// Returns True if the expression is valid and can be evaluated for this frame. + /// + public bool ValidateExpression (string expression) + { + return ValidateExpression (expression, session.EvaluationOptions); + } + + /// + /// Returns True if the expression is valid and can be evaluated for this frame. + /// + public ValidationResult ValidateExpression (string expression, EvaluationOptions options) + { + if (options.UseExternalTypeResolver) + expression = ResolveExpression (expression); + + return sourceBacktrace.ValidateExpression (index, expression, options); + } + + public CompletionData GetExpressionCompletionData (string exp) + { + return hasDebugInfo ? sourceBacktrace.GetExpressionCompletionData (index, exp) : null; + } + + // Returns disassembled code for this stack frame. + // firstLine is the relative code line. It can be negative. + public AssemblyLine[] Disassemble (int firstLine, int count) + { + return sourceBacktrace.Disassemble (index, firstLine, count); + } + + public override string ToString() + { + string loc; + + if (location.Line != -1 && !string.IsNullOrEmpty (location.FileName)) { + loc = " at " + location.FileName + ":" + location.Line; + if (location.Column != 0) + loc += "," + location.Column; + } else if (!string.IsNullOrEmpty (location.FileName)) { + loc = " at " + location.FileName; + } else { + loc = string.Empty; + } + + return string.Format ("0x{0:X} in {1}{2}", address, location.MethodName, loc); + } + + public void UpdateSourceFile (string newFilePath) + { + location = new SourceLocation (location.MethodName, newFilePath, location.Line, location.Column, location.EndLine, location.EndColumn, location.FileHash, location.SourceLink); + } + } + + [Serializable] + public struct ValidationResult + { + readonly string message; + readonly bool isValid; + + public ValidationResult (bool isValid, string message) + { + this.isValid = isValid; + this.message = message; + } + + public bool IsValid { get { return isValid; } } + public string Message { get { return message; } } + + public static implicit operator bool (ValidationResult result) + { + return result.isValid; + } + } +} diff --git a/VisualPascalABCNETLinux/MonoDebugging/Client/TargetEventArgs.cs b/VisualPascalABCNETLinux/MonoDebugging/Client/TargetEventArgs.cs new file mode 100644 index 000000000..286d6c2f7 --- /dev/null +++ b/VisualPascalABCNETLinux/MonoDebugging/Client/TargetEventArgs.cs @@ -0,0 +1,61 @@ +using System; +using Mono.Debugging.Backend; + +namespace Mono.Debugging.Client +{ + [Serializable] + public class TargetEventArgs: EventArgs + { + TargetEventType type; + Backtrace backtrace; + ProcessInfo process; + ThreadInfo thread; + + public TargetEventArgs (TargetEventType type) + { + this.type = type; + } + + public TargetEventType Type + { + get { return type; } + set { type = value; } + } + + public Backtrace Backtrace + { + get { return backtrace; } + set { backtrace = value; } + } + + public ThreadInfo Thread { + get { + return thread; + } + set { + thread = value; + } + } + + public ProcessInfo Process { + get { + return process; + } + set { + process = value; + } + } + + public bool IsStopEvent { + get; set; + } + + public BreakEvent BreakEvent { + get; set; + } + + public int? ExitCode { + get; set; + } + } +} diff --git a/VisualPascalABCNETLinux/MonoDebugging/Client/TargetEventType.cs b/VisualPascalABCNETLinux/MonoDebugging/Client/TargetEventType.cs new file mode 100644 index 000000000..f93a6cf9a --- /dev/null +++ b/VisualPascalABCNETLinux/MonoDebugging/Client/TargetEventType.cs @@ -0,0 +1,19 @@ +using System; + +namespace Mono.Debugging.Client +{ + [Serializable] + public enum TargetEventType + { + TargetReady, + TargetStopped, + TargetInterrupted, + TargetHitBreakpoint, + TargetSignaled, + TargetExited, + ExceptionThrown, + UnhandledException, + ThreadStarted, + ThreadStopped + } +} diff --git a/VisualPascalABCNETLinux/MonoDebugging/Client/ThreadEventArgs.cs b/VisualPascalABCNETLinux/MonoDebugging/Client/ThreadEventArgs.cs new file mode 100644 index 000000000..c2658d8bc --- /dev/null +++ b/VisualPascalABCNETLinux/MonoDebugging/Client/ThreadEventArgs.cs @@ -0,0 +1,43 @@ +// ThreadEventArgs.cs +// +// Author: +// Lluis Sanchez Gual +// +// Copyright (c) 2008 Novell, Inc (http://www.novell.com) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// +// + +using System; + +namespace Mono.Debugging.Client +{ + public class ThreadEventArgs : EventArgs + { + public ThreadEventArgs (int threadId) + { + ThreadId = threadId; + } + + public int ThreadId { + get; private set; + } + } +} diff --git a/VisualPascalABCNETLinux/MonoDebugging/Client/ThreadInfo.cs b/VisualPascalABCNETLinux/MonoDebugging/Client/ThreadInfo.cs new file mode 100644 index 000000000..e2c43416f --- /dev/null +++ b/VisualPascalABCNETLinux/MonoDebugging/Client/ThreadInfo.cs @@ -0,0 +1,146 @@ +// ThreadInfo.cs +// +// Author: +// Lluis Sanchez Gual +// +// Copyright (c) 2008 Novell, Inc (http://www.novell.com) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// +// + +using System; + +namespace Mono.Debugging.Client +{ + [Serializable] + public class ThreadInfo + { + long id; + string name; + long processId; + string location; + Backtrace backtrace; + + [NonSerialized] + DebuggerSession session; + + internal void Attach (DebuggerSession session) + { + this.session = session; + } + + public long Id { + get { + return id; + } + } + + public string Name { + get { + return name; + } + } + + public string Location { + get { + if (location == null) { + PrefetchBacktrace (); + if (backtrace != null && backtrace.FrameCount > 0) + location = backtrace.GetFrame (0).ToString (); + } + return location; + } + } + + internal long ProcessId { + get { return processId; } + } + + public Backtrace Backtrace { + get { + PrefetchBacktrace (); + return backtrace; + } + } + + public long ElapsedTime { + get { + var elapsedTime = session.GetElapsedTime (processId, id); + return elapsedTime; + } + } + + public void SetActive () + { + session.ActiveThread = this; + } + + public void PrefetchBacktrace () + { + if (backtrace == null) + backtrace = session.GetBacktrace (processId, id); + } + + public ThreadInfo (long processId, long id, string name, string location): this (processId, id, name, location, null) + { + } + + public ThreadInfo (long processId, long id, string name, string location, Backtrace backtrace) + { + this.id = id; + this.name = name; + this.processId = processId; + this.location = location; + this.backtrace = backtrace; + } + + public override bool Equals (object obj) + { + if (obj is ThreadInfo ot) + return id == ot.id && processId == ot.processId && session == ot.session; + return false; + } + + public override int GetHashCode () + { + unchecked { + return (int) (id + processId*1000); + } + } + + public static bool operator == (ThreadInfo t1, ThreadInfo t2) + { + if (object.ReferenceEquals (t1, t2)) + return true; + if ((object)t1 == null || (object)t2 == null) + return false; + return t1.Equals (t2); + } + + public static bool operator != (ThreadInfo t1, ThreadInfo t2) + { + if (object.ReferenceEquals (t1, t2)) + return false; + if (t1 == null || t2 == null) + return true; + return !t1.Equals (t2); + } + } +} diff --git a/VisualPascalABCNETLinux/MonoDebugging/Client/UsageCounter.cs b/VisualPascalABCNETLinux/MonoDebugging/Client/UsageCounter.cs new file mode 100644 index 000000000..b469ddf15 --- /dev/null +++ b/VisualPascalABCNETLinux/MonoDebugging/Client/UsageCounter.cs @@ -0,0 +1,55 @@ +// +// UsageCounter.cs +// +// Author: +// Jeffrey Stedfast +// +// Copyright (c) 2020 Microsoft Corp. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +using System.Collections.Generic; + +namespace Mono.Debugging.Client +{ + public class UsageCounter + { + readonly Dictionary stats = new Dictionary (); + + public UsageCounter () + { + } + + public bool HasUsageStats { + get { return stats.Count > 0; } + } + + public void IncrementUsage (string action) + { + stats.TryGetValue (action, out var value); + stats[action] = value + 1; + } + + public void Serialize (Dictionary metadata) + { + foreach (var kvp in stats) + metadata[kvp.Key] = kvp.Value; + } + } +} diff --git a/VisualPascalABCNETLinux/MonoDebugging/Evaluation/ArrayElementGroup.cs b/VisualPascalABCNETLinux/MonoDebugging/Evaluation/ArrayElementGroup.cs new file mode 100644 index 000000000..d9bd62795 --- /dev/null +++ b/VisualPascalABCNETLinux/MonoDebugging/Evaluation/ArrayElementGroup.cs @@ -0,0 +1,382 @@ +// ArrayElementGroup.cs +// +// Authors: Lluis Sanchez Gual +// Jeffrey Stedfast +// +// Copyright (c) 2008 Novell, Inc (http://www.novell.com) +// Copyright (c) 2012 Xamarin Inc. (http://www.xamarin.com) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// +// + +using System; +using System.Collections.Generic; +using System.Text; +using Mono.Debugging.Client; +using Mono.Debugging.Backend; + +namespace Mono.Debugging.Evaluation +{ + public class ArrayElementGroup: RemoteFrameObject, IObjectValueSource + { + readonly ICollectionAdaptor array; + readonly EvaluationContext ctx; + int[] baseIndices; + int[] dimensions; + int firstIndex; + int lastIndex; + + const int MaxChildCount = 150; + + public ArrayElementGroup (EvaluationContext ctx, ICollectionAdaptor array) + : this (ctx, array, new int [0]) + { + } + + public ArrayElementGroup (EvaluationContext ctx, ICollectionAdaptor array, int[] baseIndices) + : this (ctx, array, baseIndices, 0, -1) + { + } + + public ArrayElementGroup (EvaluationContext ctx, ICollectionAdaptor array, int[] baseIndices, int firstIndex, int lastIndex) + { + this.array = array; + this.ctx = ctx; + this.dimensions = array.GetDimensions (); + this.baseIndices = baseIndices; + this.firstIndex = firstIndex; + this.lastIndex = lastIndex; + } + + public bool IsRange { + get { return lastIndex != -1; } + } + + public ObjectValue CreateObjectValue () + { + Connect (); + + var sb = new StringBuilder ("["); + + for (int i = 0; i < baseIndices.Length; i++) { + if (i > 0) + sb.Append (", "); + sb.Append (baseIndices[i].ToString ()); + } + + if (IsRange) { + if (baseIndices.Length > 0) + sb.Append (", "); + + sb.Append (firstIndex.ToString ()).Append ("..").Append (lastIndex.ToString ()); + } + + if (dimensions.Length > 1 && baseIndices.Length < dimensions.Length) + sb.Append (", ..."); + + sb.Append ("]"); + + ObjectValue res = ObjectValue.CreateObject (this, new ObjectPath (sb.ToString ()), "", "", ObjectValueFlags.ArrayElement|ObjectValueFlags.ReadOnly|ObjectValueFlags.NoRefresh, null); + res.ChildSelector = ""; + return res; + } + + public ObjectValue[] GetChildren (EvaluationOptions options) + { + return GetChildren (new ObjectPath ("this"), -1, -1, options); + } + + public ObjectValue[] GetChildren (ObjectPath path, int firstItemIndex, int count, EvaluationOptions options) + { + EvaluationContext cctx = ctx.WithOptions (options); + if (path.Length > 1) { + // Looking for children of an array element + int[] idx = StringToIndices (path [1]); + object obj = array.GetElement (idx); + return cctx.Adapter.GetObjectValueChildren (cctx, new ArrayObjectSource (array, path[1]), obj, firstItemIndex, count); + } + + int lowerBound; + int upperBound; + bool isLastDimension; + + if (dimensions.Length > 1) { + int rank = baseIndices.Length; + lowerBound = array.GetLowerBounds () [rank]; + upperBound = lowerBound + dimensions [rank] - 1; + isLastDimension = rank == dimensions.Length - 1; + } else { + lowerBound = array.GetLowerBounds () [0]; + upperBound = lowerBound + dimensions [0] - 1; + isLastDimension = true; + } + + int len; + int initalIndex; + + if (!IsRange) { + initalIndex = lowerBound; + len = upperBound + 1 - lowerBound; + } else { + initalIndex = firstIndex; + len = lastIndex - firstIndex + 1; + } + + if (firstItemIndex == -1) { + firstItemIndex = 0; + count = len; + } + + // Make sure the group doesn't have too many elements. If so, divide + int div = 1; + while (len / div > MaxChildCount) + div *= 10; + + if (div == 1 && isLastDimension) { + // Return array elements + + ObjectValue[] values = new ObjectValue [count]; + ObjectPath newPath = new ObjectPath ("this"); + + int[] curIndex = new int [baseIndices.Length + 1]; + Array.Copy (baseIndices, curIndex, baseIndices.Length); + string curIndexStr = IndicesToString (baseIndices); + if (baseIndices.Length > 0) + curIndexStr += ","; + curIndex [curIndex.Length - 1] = initalIndex + firstItemIndex; + var elems = array.GetElements (curIndex, System.Math.Min (values.Length, upperBound - lowerBound + 1)); + + for (int n = 0; n < values.Length; n++) { + int index = n + initalIndex + firstItemIndex; + string sidx = curIndexStr + index; + ObjectValue val; + string ename = "[" + sidx.Replace (",", ", ") + "]"; + if (index > upperBound) + val = ObjectValue.CreateUnknown (sidx); + else { + curIndex [curIndex.Length - 1] = index; + val = cctx.Adapter.CreateObjectValue (cctx, this, newPath.Append (sidx), elems.GetValue (n), ObjectValueFlags.ArrayElement); + if (elems.GetValue (n) != null && !cctx.Adapter.IsNull (cctx, elems.GetValue (n))) { + TypeDisplayData tdata = cctx.Adapter.GetTypeDisplayData (cctx, cctx.Adapter.GetValueType (cctx, elems.GetValue (n))); + if (!string.IsNullOrEmpty (tdata.NameDisplayString)) { + try { + ename = cctx.Adapter.EvaluateDisplayString (cctx, elems.GetValue (n), tdata.NameDisplayString); + } catch (MissingMemberException) { + // missing property or otherwise malformed DebuggerDisplay string + } + } + } + } + val.Name = ename; + values [n] = val; + } + return values; + } + + if (!isLastDimension && div == 1) { + // Return an array element group for each index + + var list = new List (); + for (int i=0; i upperBound) + val = ObjectValue.CreateUnknown (""); + else { + ArrayElementGroup grp = new ArrayElementGroup (cctx, array, curIndex); + val = grp.CreateObjectValue (); + } + list.Add (val); + } + return list.ToArray (); + } else { + // Too many elements. Split the array. + + // Don't make divisions of 10 elements, min is 100 + if (div == 10) + div = 100; + + // Create the child groups + int i = initalIndex + firstItemIndex; + len += i; + var list = new List (); + while (i < len) { + int end = i + div - 1; + if (end >= len) + end = len - 1; + ArrayElementGroup grp = new ArrayElementGroup (cctx, array, baseIndices, i, end); + list.Add (grp.CreateObjectValue ()); + i += div; + } + return list.ToArray (); + } + } + + internal static string IndicesToString (int[] indices) + { + var sb = new StringBuilder (); + + for (int i = 0; i < indices.Length; i++) { + if (i > 0) + sb.Append (','); + sb.Append (indices[i].ToString ()); + } + + return sb.ToString (); + } + + internal static int[] StringToIndices (string str) + { + var sidx = str.Split (','); + var idx = new int [sidx.Length]; + + for (int i = 0; i < sidx.Length; i++) + idx[i] = int.Parse (sidx[i]); + + return idx; + } + + public static string GetArrayDescription (int[] bounds) + { + if (bounds.Length == 0) + return "[...]"; + + var sb = new StringBuilder ("["); + + for (int i = 0; i < bounds.Length; i++) { + if (i > 0) + sb.Append (", "); + sb.Append (bounds [i].ToString ()); + } + + sb.Append ("]"); + + return sb.ToString (); + } + + public EvaluationResult SetValue (ObjectPath path, string value, EvaluationOptions options) + { + if (path.Length != 2) + throw new NotSupportedException (); + + int[] idx = StringToIndices (path [1]); + + object val; + try { + EvaluationContext cctx = ctx.Clone (); + EvaluationOptions ops = options ?? cctx.Options; + ops.AllowMethodEvaluation = true; + ops.AllowTargetInvoke = true; + cctx.Options = ops; + ValueReference var = ctx.Evaluator.Evaluate (ctx, value, array.ElementType); + val = var.Value; + val = ctx.Adapter.Convert (ctx, val, array.ElementType); + array.SetElement (idx, val); + } catch { + val = array.GetElement (idx); + } + try { + return ctx.Evaluator.TargetObjectToExpression (ctx, val); + } catch (Exception ex) { + ctx.WriteDebuggerError (ex); + return new EvaluationResult ("? (" + ex.Message + ")"); + } + } + + public ObjectValue GetValue (ObjectPath path, EvaluationOptions options) + { + if (path.Length != 2) + throw new NotSupportedException (); + + int[] idx = StringToIndices (path [1]); + object elem = array.GetElement (idx); + EvaluationContext cctx = ctx.WithOptions (options); + ObjectValue val = cctx.Adapter.CreateObjectValue (cctx, this, path, elem, ObjectValueFlags.ArrayElement); + if (elem != null && !cctx.Adapter.IsNull (cctx, elem)) { + TypeDisplayData tdata = cctx.Adapter.GetTypeDisplayData (cctx, cctx.Adapter.GetValueType (cctx, elem)); + if (!string.IsNullOrEmpty (tdata.NameDisplayString)) { + try { + val.Name = cctx.Adapter.EvaluateDisplayString (cctx, elem, tdata.NameDisplayString); + } catch (MissingMemberException) { + // missing property or otherwise malformed DebuggerDisplay string + } + } + } + return val; + } + + public object GetRawValue (ObjectPath path, EvaluationOptions options) + { + if (path.Length != 2) + throw new NotSupportedException (); + + int[] idx = StringToIndices (path [1]); + object elem = array.GetElement (idx); + EvaluationContext cctx = ctx.WithOptions (options); + return cctx.Adapter.ToRawValue (cctx, new ArrayObjectSource (array, idx), elem); + } + + public void SetRawValue (ObjectPath path, object value, EvaluationOptions options) + { + if (path.Length != 2) + throw new NotSupportedException (); + + int[] idx = StringToIndices (path [1]); + + EvaluationContext cctx = ctx.WithOptions (options); + object val = cctx.Adapter.FromRawValue (cctx, value); + array.SetElement (idx, val); + } + } + + class ArrayObjectSource: IObjectSource + { + readonly ICollectionAdaptor source; + readonly string path; + + public ArrayObjectSource (ICollectionAdaptor source, string path) + { + this.source = source; + this.path = path; + } + + public ArrayObjectSource (ICollectionAdaptor source, int[] index) + { + this.source = source; + this.path = ArrayElementGroup.IndicesToString (index); + } + + public object Value { + get { + return source.GetElement (ArrayElementGroup.StringToIndices (path)); + } + set { + source.SetElement (ArrayElementGroup.StringToIndices (path), value); + } + } + } +} diff --git a/VisualPascalABCNETLinux/MonoDebugging/Evaluation/ArrayValueReference.cs b/VisualPascalABCNETLinux/MonoDebugging/Evaluation/ArrayValueReference.cs new file mode 100644 index 000000000..0d2c4109e --- /dev/null +++ b/VisualPascalABCNETLinux/MonoDebugging/Evaluation/ArrayValueReference.cs @@ -0,0 +1,82 @@ +// ArrayValueReference.cs +// +// Author: +// Lluis Sanchez Gual +// +// Copyright (c) 2008 Novell, Inc (http://www.novell.com) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// +// + +using System; +using System.Text; +using Mono.Debugging.Client; + +namespace Mono.Debugging.Evaluation +{ + public class ArrayValueReference: ValueReference + { + readonly ICollectionAdaptor adaptor; + readonly int[] indices; + + public ArrayValueReference (EvaluationContext ctx, object arr, int[] indices) : base (ctx) + { + this.indices = indices; + adaptor = ctx.Adapter.CreateArrayAdaptor (ctx, arr); + } + + public override object Value { + get { + return adaptor.GetElement (indices); + } + set { + adaptor.SetElement (indices, value); + } + } + + public override string Name { + get { + var name = new StringBuilder (); + + name.Append ('['); + for (int n = 0; n < indices.Length; n++) { + if (n > 0) + name.Append (", "); + name.Append (indices[n]); + } + name.Append (']'); + + return name.ToString (); + } + } + + public override object Type { + get { + return adaptor.ElementType; + } + } + + public override ObjectValueFlags Flags { + get { + return ObjectValueFlags.ArrayElement; + } + } + } +} diff --git a/VisualPascalABCNETLinux/MonoDebugging/Evaluation/AsyncEvaluationTracker.cs b/VisualPascalABCNETLinux/MonoDebugging/Evaluation/AsyncEvaluationTracker.cs new file mode 100644 index 000000000..05810282d --- /dev/null +++ b/VisualPascalABCNETLinux/MonoDebugging/Evaluation/AsyncEvaluationTracker.cs @@ -0,0 +1,163 @@ +// AsyncEvaluationTracker.cs +// +// Author: +// Lluis Sanchez Gual +// +// Copyright (c) 2008 Novell, Inc (http://www.novell.com) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// +// + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using Mono.Debugging.Client; +using Mono.Debugging.Backend; + +namespace Mono.Debugging.Evaluation +{ + public delegate ObjectValue ObjectEvaluatorDelegate (); + + /// + /// This class can be used to generate an ObjectValue using a provided evaluation delegate. + /// The value is initialy evaluated synchronously (blocking the caller). If no result + /// is obtained after a short period (provided in the WaitTime property), evaluation + /// will then be made asynchronous and the Run method will immediately return an ObjectValue + /// with the Evaluating state. + /// + public class AsyncEvaluationTracker: RemoteFrameObject, IObjectValueUpdater, IDisposable + { + Dictionary asyncCallbacks = new Dictionary (); + Dictionary asyncResults = new Dictionary (); + int asyncCounter = 0; + int cancelTimestamp = 0; + TimedEvaluator runner = new TimedEvaluator (); + + public int WaitTime { + get { return runner.RunTimeout; } + set { runner.RunTimeout = value; } + } + + public bool IsEvaluating { + get { return runner.IsEvaluating; } + } + + internal DebuggerSession Session { get; set; } + + public ObjectValue Run (string name, ObjectValueFlags flags, ObjectEvaluatorDelegate evaluator) + { + string id; + int tid; + lock (asyncCallbacks) { + tid = asyncCounter++; + id = tid.ToString (); + } + + ObjectValue val = null; + bool done = runner.Run (delegate { + if (tid >= cancelTimestamp) { + var session = Session; + if (session == null || (flags == ObjectValueFlags.EvaluatingGroup)) { + // Cannot report timing if session is null. If a group is being + // evaluated then individual timings are not possible and must + // be done elsewhere. + val = evaluator (); + } else { + using (var timer = session.EvaluationStats.StartTimer (name)) { + val = evaluator (); + timer.Stop (val); + } + } + } + }, + delegate { + if (tid >= cancelTimestamp) + OnEvaluationDone (id, val); + }); + + if (done) { + // 'val' may be null if the timed evaluator is disposed while evaluating + return val ?? ObjectValue.CreateUnknown (name); + } + + return ObjectValue.CreateEvaluating (this, new ObjectPath (id, name), flags); + } + + public void Dispose () + { + runner.Dispose (); + } + + + public void Stop () + { + lock (asyncCallbacks) { + cancelTimestamp = asyncCounter; + runner.CancelAll (); + foreach (var cb in asyncCallbacks.Values) { + try { + cb.UpdateValue (ObjectValue.CreateFatalError ("", "Canceled", ObjectValueFlags.None)); + } catch { + } + } + asyncCallbacks.Clear (); + asyncResults.Clear (); + } + } + + public void WaitForStopped () + { + runner.WaitForStopped (); + } + + void OnEvaluationDone (string id, ObjectValue val) + { + if (val == null) + val = ObjectValue.CreateUnknown (null); + UpdateCallback cb = null; + lock (asyncCallbacks) { + if (asyncCallbacks.TryGetValue (id, out cb)) { + try { + cb.UpdateValue (val); + } catch {} + asyncCallbacks.Remove (id); + } + else + asyncResults [id] = val; + } + } + + void IObjectValueUpdater.RegisterUpdateCallbacks (UpdateCallback[] callbacks) + { + foreach (UpdateCallback c in callbacks) { + lock (asyncCallbacks) { + ObjectValue val; + string id = c.Path[0]; + if (asyncResults.TryGetValue (id, out val)) { + c.UpdateValue (val); + asyncResults.Remove (id); + } else { + asyncCallbacks [id] = c; + } + } + } + } + } +} diff --git a/VisualPascalABCNETLinux/MonoDebugging/Evaluation/AsyncOperationManager.cs b/VisualPascalABCNETLinux/MonoDebugging/Evaluation/AsyncOperationManager.cs new file mode 100644 index 000000000..c3709590b --- /dev/null +++ b/VisualPascalABCNETLinux/MonoDebugging/Evaluation/AsyncOperationManager.cs @@ -0,0 +1,257 @@ +// RuntimeInvokeManager.cs +// +// Author: +// Lluis Sanchez Gual +// +// Copyright (c) 2008 Novell, Inc (http://www.novell.com) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// +// + +using System; +using System.Collections.Generic; +using ST = System.Threading; +using Mono.Debugging.Client; + +namespace Mono.Debugging.Evaluation +{ + public class AsyncOperationManager: IDisposable + { + readonly List operationsToCancel = new List (); + internal bool Disposing; + + public void Invoke (AsyncOperation methodCall, int timeout) + { + methodCall.Aborted = false; + methodCall.Manager = this; + + lock (operationsToCancel) { + operationsToCancel.Add (methodCall); + methodCall.Invoke (); + } + + if (timeout > 0) { + if (!methodCall.WaitForCompleted (timeout)) { + var wasAborted = methodCall.Aborted; + + methodCall.InternalAbort (); + + lock (operationsToCancel) { + operationsToCancel.Remove (methodCall); + ST.Monitor.PulseAll (operationsToCancel); + } + + if (wasAborted) + throw new EvaluatorAbortedException (); + + throw new TimeOutException (); + } + } + else { + methodCall.WaitForCompleted (System.Threading.Timeout.Infinite); + } + + lock (operationsToCancel) { + operationsToCancel.Remove (methodCall); + ST.Monitor.PulseAll (operationsToCancel); + if (methodCall.Aborted) { + throw new EvaluatorAbortedException (); + } + } + + if (!string.IsNullOrEmpty (methodCall.ExceptionMessage)) { + throw new Exception (methodCall.ExceptionMessage); + } + } + + public void Dispose () + { + Disposing = true; + lock (operationsToCancel) { + foreach (var op in operationsToCancel) { + op.InternalShutdown (); + } + operationsToCancel.Clear (); + } + } + + public void AbortAll () + { + lock (operationsToCancel) { + foreach (var op in operationsToCancel) + op.InternalAbort (); + } + } + + public void EnterBusyState (AsyncOperation oper) + { + var args = new BusyStateEventArgs { + IsBusy = true, + Description = oper.Description, + EvaluationContext = oper.EvaluationContext + }; + + BusyStateChanged?.Invoke (this, args); + } + + public void LeaveBusyState (AsyncOperation oper) + { + var args = new BusyStateEventArgs { + IsBusy = false, + Description = oper.Description, + EvaluationContext = oper.EvaluationContext + }; + + BusyStateChanged?.Invoke (this, args); + } + + public event EventHandler BusyStateChanged; + } + + public abstract class AsyncOperation + { + internal AsyncOperationManager Manager; + internal bool Aborted; + + public bool Aborting { get; internal set; } + + public EvaluationContext EvaluationContext { + get; private set; + } + + protected AsyncOperation (EvaluationContext ctx) + { + EvaluationContext = ctx; + } + + internal void InternalAbort () + { + ST.Monitor.Enter (this); + if (Aborted) { + ST.Monitor.Exit (this); + return; + } + + if (Aborting) { + // Somebody else is aborting this. Just wait for it to finish. + ST.Monitor.Exit (this); + WaitForCompleted (ST.Timeout.Infinite); + return; + } + + Aborting = true; + + int abortState = 0; + int abortRetryWait = 100; + bool abortRequested = false; + + do { + if (abortState > 0) + ST.Monitor.Enter (this); + + try { + if (!Aborted && !abortRequested) { + // The Abort() call doesn't block. WaitForCompleted is used below to wait for the abort to succeed + Abort (); + abortRequested = true; + } + // Short wait for the Abort to finish. If this wait is not enough, it will wait again in the next loop + if (WaitForCompleted (100)) { + ST.Monitor.Exit (this); + break; + } + } catch { + // If abort fails, try again after a short wait + } + abortState++; + if (abortState == 6) { + // Several abort calls have failed. Inform the user that the debugger is busy + abortRetryWait = 500; + try { + Manager.EnterBusyState (this); + } catch (Exception ex) { + Console.WriteLine (ex); + } + } + ST.Monitor.Exit (this); + } while (!Aborted && !WaitForCompleted (abortRetryWait) && !Manager.Disposing); + + if (Manager.Disposing) { + InternalShutdown (); + } + else { + lock (this) { + Aborted = true; + if (abortState >= 6) + Manager.LeaveBusyState (this); + } + } + } + + internal void InternalShutdown () + { + lock (this) { + if (Aborted) + return; + try { + Aborted = true; + Shutdown (); + } catch { + // Ignore + } + } + } + + /// + /// Message of the exception, if the execution failed. + /// + public string ExceptionMessage { get; set; } + + /// + /// Returns a short description of the operation, to be shown in the Debugger Busy Dialog + /// when it blocks the execution of the debugger. + /// + public abstract string Description { get; } + + /// + /// Called to invoke the operation. The execution must be asynchronous (it must return immediatelly). + /// + public abstract void Invoke ( ); + + /// + /// Called to abort the execution of the operation. It has to throw an exception + /// if the operation can't be aborted. This operation must not block. The engine + /// will wait for the operation to be aborted by calling WaitForCompleted. + /// + public abstract void Abort (); + + /// + /// Waits until the operation has been completed or aborted. + /// + public abstract bool WaitForCompleted (int timeout); + + /// + /// Called when the debugging session has been disposed. + /// I must cause any call to WaitForCompleted to exit, even if the operation + /// has not been completed or can't be aborted. + /// + public abstract void Shutdown (); + } +} diff --git a/VisualPascalABCNETLinux/MonoDebugging/Evaluation/BaseBacktrace.cs b/VisualPascalABCNETLinux/MonoDebugging/Evaluation/BaseBacktrace.cs new file mode 100644 index 000000000..a7caf1a02 --- /dev/null +++ b/VisualPascalABCNETLinux/MonoDebugging/Evaluation/BaseBacktrace.cs @@ -0,0 +1,296 @@ +// +// Backtrace.cs +// +// Author: +// Lluis Sanchez Gual +// +// Copyright (c) 2009 Novell, Inc (http://www.novell.com) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +using System; +using System.Collections.Generic; + +using Mono.Debugging.Client; +using Mono.Debugging.Backend; + +namespace Mono.Debugging.Evaluation +{ + public abstract class BaseBacktrace: RemoteFrameObject, IBacktrace + { + readonly Dictionary frameInfo = new Dictionary (); + + protected BaseBacktrace (ObjectValueAdaptor adaptor) + { + Adaptor = adaptor; + } + + public abstract StackFrame[] GetStackFrames (int firstIndex, int lastIndex); + + public ObjectValueAdaptor Adaptor { get; set; } + + protected abstract EvaluationContext GetEvaluationContext (int frameIndex, EvaluationOptions options); + + public abstract int FrameCount { get; } + + public virtual ObjectValue[] GetLocalVariables (int frameIndex, EvaluationOptions options) + { + var frame = GetFrameInfo (frameIndex, options, false); + var list = new List (); + + if (frame == null) { + var val = Adaptor.CreateObjectValueAsync ("Local Variables", ObjectValueFlags.EvaluatingGroup, delegate { + frame = GetFrameInfo (frameIndex, options, true); + foreach (var local in frame.LocalVariables) { + using (var timer = StartEvaluationTimer (local.Name)) { + var localValue = local.CreateObjectValue (false, options); + timer.Stop (localValue); + list.Add (localValue); + } + } + + return ObjectValue.CreateArray (null, new ObjectPath ("Local Variables"), "", list.Count, ObjectValueFlags.EvaluatingGroup, list.ToArray ()); + }); + + return new [] { val }; + } + + foreach (var local in frame.LocalVariables) + list.Add (local.CreateObjectValue (true, options)); + + return list.ToArray (); + } + + public virtual ObjectValue[] GetParameters (int frameIndex, EvaluationOptions options) + { + var frame = GetFrameInfo (frameIndex, options, false); + var values = new List (); + + if (frame == null) { + var value = Adaptor.CreateObjectValueAsync ("Parameters", ObjectValueFlags.EvaluatingGroup, delegate { + frame = GetFrameInfo (frameIndex, options, true); + foreach (var param in frame.Parameters) { + using (var timer = StartEvaluationTimer (param.Name)) { + var paramValue = param.CreateObjectValue (false, options); + timer.Stop (paramValue); + values.Add (paramValue); + } + } + + return ObjectValue.CreateArray (null, new ObjectPath ("Parameters"), "", values.Count, ObjectValueFlags.EvaluatingGroup, values.ToArray ()); + }); + + return new [] { value }; + } + + foreach (var param in frame.Parameters) + values.Add (param.CreateObjectValue (true, options)); + + return values.ToArray (); + } + + public virtual ObjectValue GetThisReference (int frameIndex, EvaluationOptions options) + { + var frame = GetFrameInfo (frameIndex, options, false); + + if (frame == null) { + return Adaptor.CreateObjectValueAsync ("this", ObjectValueFlags.EvaluatingGroup, delegate { + frame = GetFrameInfo (frameIndex, options, true); + ObjectValue[] values; + + if (frame.This != null) { + using (var timer = StartEvaluationTimer ("this")) { + var thisValue = frame.This.CreateObjectValue (false, options); + timer.Stop (thisValue); + values = new [] { thisValue }; + } + } else + values = new ObjectValue [0]; + + return ObjectValue.CreateArray (null, new ObjectPath ("this"), "", values.Length, ObjectValueFlags.EvaluatingGroup, values); + }); + } + + return frame.This != null ? frame.This.CreateObjectValue (true, options) : null; + } + + public virtual ExceptionInfo GetException (int frameIndex, EvaluationOptions options) + { + var frame = GetFrameInfo (frameIndex, options, false); + ObjectValue value; + + if (frame == null) { + value = Adaptor.CreateObjectValueAsync (options.CurrentExceptionTag, ObjectValueFlags.EvaluatingGroup, delegate { + frame = GetFrameInfo (frameIndex, options, true); + ObjectValue[] values; + + if (frame.Exception != null) { + using (var timer = StartEvaluationTimer ("Exception")) { + var exceptionValue = frame.Exception.CreateObjectValue (false, options); + timer.Stop (exceptionValue); + values = new [] { exceptionValue }; + } + } else + values = new ObjectValue [0]; + + return ObjectValue.CreateArray (null, new ObjectPath (options.CurrentExceptionTag), "", values.Length, ObjectValueFlags.EvaluatingGroup, values); + }); + } else if (frame.Exception != null) { + value = frame.Exception.CreateObjectValue (true, options); + } else { + return null; + } + + return new ExceptionInfo (value); + } + + public virtual ObjectValue GetExceptionInstance (int frameIndex, EvaluationOptions options) + { + var frame = GetFrameInfo (frameIndex, options, false); + + if (frame == null) { + return Adaptor.CreateObjectValueAsync (options.CurrentExceptionTag, ObjectValueFlags.EvaluatingGroup, delegate { + frame = GetFrameInfo (frameIndex, options, true); + ObjectValue[] values; + + if (frame.Exception != null) { + using (var timer = StartEvaluationTimer ("Exception")) { + var exceptionValue = frame.Exception.Exception.CreateObjectValue (false, options); + timer.Stop (exceptionValue); + values = new [] { exceptionValue }; + } + } else + values = new ObjectValue [0]; + + return ObjectValue.CreateArray (null, new ObjectPath (options.CurrentExceptionTag), "", values.Length, ObjectValueFlags.EvaluatingGroup, values); + }); + } + + return frame.Exception != null ? frame.Exception.Exception.CreateObjectValue (true, options) : null; + } + + public virtual ObjectValue[] GetAllLocals (int frameIndex, EvaluationOptions options) + { + var locals = new List (); + + var excObj = GetExceptionInstance (frameIndex, options); + if (excObj != null) + locals.Insert (0, excObj); + + locals.AddRange (GetLocalVariables (frameIndex, options)); + locals.AddRange (GetParameters (frameIndex, options)); + + locals.Sort ((v1, v2) => StringComparer.InvariantCulture.Compare (v1.Name, v2.Name)); + + var thisObj = GetThisReference (frameIndex, options); + if (thisObj != null) + locals.Insert (0, thisObj); + + return locals.ToArray (); + } + + public virtual ObjectValue[] GetExpressionValues (int frameIndex, string[] expressions, EvaluationOptions options) + { + if (Adaptor.IsEvaluating) { + var values = new List (); + + foreach (var expression in expressions) { + string tmpExp = expression; + + var value = Adaptor.CreateObjectValueAsync (tmpExp, ObjectValueFlags.Field, delegate { + var cctx = GetEvaluationContext (frameIndex, options); + return Adaptor.GetExpressionValue (cctx, tmpExp); + }); + value.Name = expression; + + values.Add (value); + } + + return values.ToArray (); + } + + var ctx = GetEvaluationContext (frameIndex, options); + + return ctx.Adapter.GetExpressionValuesAsync (ctx, expressions); + } + + public virtual CompletionData GetExpressionCompletionData (int frameIndex, string exp) + { + var ctx = GetEvaluationContext (frameIndex, EvaluationOptions.DefaultOptions); + return ctx.Adapter.GetExpressionCompletionData (ctx, exp); + } + + public virtual AssemblyLine[] Disassemble (int frameIndex, int firstLine, int count) + { + throw new NotImplementedException (); + } + + public virtual ValidationResult ValidateExpression (int frameIndex, string expression, EvaluationOptions options) + { + var ctx = GetEvaluationContext (frameIndex, options); + return Adaptor.ValidateExpression (ctx, expression); + } + + FrameInfo GetFrameInfo (int frameIndex, EvaluationOptions options, bool ignoreEvalStatus) + { + FrameInfo finfo; + + lock (frameInfo) { + if (frameInfo.TryGetValue (frameIndex, out finfo)) + return finfo; + + if (!ignoreEvalStatus && Adaptor.IsEvaluating) + return null; + + var ctx = GetEvaluationContext (frameIndex, options); + if (ctx == null) + return null; + + finfo = new FrameInfo (); + finfo.Context = ctx; + //Don't try to optimize lines below with lazy loading, you won't gain anything(in communication with runtime) + finfo.LocalVariables.AddRange (ctx.Evaluator.GetLocalVariables (ctx)); + finfo.Parameters.AddRange (ctx.Evaluator.GetParameters (ctx)); + finfo.This = ctx.Evaluator.GetThisReference (ctx); + + var ex = ctx.Evaluator.GetCurrentException (ctx); + if (ex != null) + finfo.Exception = new ExceptionInfoSource (ctx, ex); + + frameInfo[frameIndex] = finfo; + } + + return finfo; + } + + DebuggerTimer StartEvaluationTimer (string name) + { + return new DebuggerTimer (Adaptor.DebuggerSession?.EvaluationStats, name); + } + } + + class FrameInfo + { + public EvaluationContext Context; + public List LocalVariables = new List (); + public List Parameters = new List (); + public ValueReference This; + public ExceptionInfoSource Exception; + } +} diff --git a/VisualPascalABCNETLinux/MonoDebugging/Evaluation/BaseTypeViewSource.cs b/VisualPascalABCNETLinux/MonoDebugging/Evaluation/BaseTypeViewSource.cs new file mode 100644 index 000000000..fd871e9e8 --- /dev/null +++ b/VisualPascalABCNETLinux/MonoDebugging/Evaluation/BaseTypeViewSource.cs @@ -0,0 +1,89 @@ +// +// BaseTypeViewSource.cs +// +// Authors: Lluis Sanchez Gual +// Jeffrey Stedfast +// +// Copyright (c) 2009 Novell, Inc (http://www.novell.com) +// Copyright (c) 2012 Xamarin Inc. (http://www.xamarin.com) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +using System; +using Mono.Debugging.Backend; +using Mono.Debugging.Client; + +namespace Mono.Debugging.Evaluation +{ + public class BaseTypeViewSource: RemoteFrameObject, IObjectValueSource + { + EvaluationContext ctx; + object type; + object obj; + IObjectSource objectSource; + + public BaseTypeViewSource (EvaluationContext ctx, IObjectSource objectSource, object type, object obj) + { + this.ctx = ctx; + this.type = type; + this.obj = obj; + this.objectSource = objectSource; + } + + public static ObjectValue CreateBaseTypeView (EvaluationContext ctx, IObjectSource objectSource, object type, object obj) + { + BaseTypeViewSource src = new BaseTypeViewSource (ctx, objectSource, type, obj); + src.Connect (); + string tname = ctx.Adapter.GetDisplayTypeName (ctx, type); + ObjectValue val = ObjectValue.CreateObject (src, new ObjectPath ("base"), tname, "{" + tname + "}", ObjectValueFlags.Type|ObjectValueFlags.ReadOnly|ObjectValueFlags.NoRefresh, null); + val.ChildSelector = ""; + return val; + } + + #region IObjectValueSource implementation + + public ObjectValue[] GetChildren (ObjectPath path, int index, int count, EvaluationOptions options) + { + EvaluationContext cctx = ctx.WithOptions (options); + return cctx.Adapter.GetObjectValueChildren (cctx, objectSource, type, obj, index, count, false); + } + + public EvaluationResult SetValue (ObjectPath path, string value, EvaluationOptions options) + { + throw new NotSupportedException (); + } + + public ObjectValue GetValue (ObjectPath path, EvaluationOptions options) + { + throw new NotSupportedException (); + } + + public object GetRawValue (ObjectPath path, EvaluationOptions options) + { + throw new NotImplementedException (); + } + + public void SetRawValue (ObjectPath path, object value, EvaluationOptions options) + { + throw new NotImplementedException (); + } + + #endregion + } +} diff --git a/VisualPascalABCNETLinux/MonoDebugging/Evaluation/EnumerableElementGroup.cs b/VisualPascalABCNETLinux/MonoDebugging/Evaluation/EnumerableElementGroup.cs new file mode 100644 index 000000000..9f023569a --- /dev/null +++ b/VisualPascalABCNETLinux/MonoDebugging/Evaluation/EnumerableElementGroup.cs @@ -0,0 +1,163 @@ +// +// IEnumerableSource.cs +// +// Author: +// David Karlaš +// +// Copyright (c) 2014 Xamarin, Inc (http://www.xamarin.com) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +using System; +using System.Linq; +using Mono.Debugging.Backend; +using Mono.Debugging.Client; +using System.Collections.Generic; + +namespace Mono.Debugging.Evaluation +{ + class EnumerableSource : IObjectValueSource + { + object obj; + object objType; + EvaluationContext ctx; + List elements; + List values; + int currentIndex = 0; + object enumerator; + object enumeratorType; + + public EnumerableSource (object source, object type, EvaluationContext ctx) + { + this.obj = source; + this.objType = type; + this.ctx = ctx; + } + + bool MoveNext () + { + return (bool)ctx.Adapter.TargetObjectToObject (ctx, ctx.Adapter.RuntimeInvoke (ctx, enumeratorType, enumerator, "MoveNext", new object[0], new object[0])); + } + + void Fetch (int maxIndex) + { + if (elements == null) { + elements = new List (); + values = new List (); + enumerator = ctx.Adapter.RuntimeInvoke (ctx, objType, obj, "GetEnumerator", new object[0], new object[0]); + enumeratorType = ctx.Adapter.GetImplementedInterfaces (ctx, ctx.Adapter.GetValueType (ctx, enumerator)).First (f => ctx.Adapter.GetTypeName (ctx, f) == "System.Collections.IEnumerator"); + } + while (maxIndex > elements.Count && MoveNext ()) { + var valCurrent = ctx.Adapter.GetMember (ctx, null, enumeratorType, enumerator, "Current"); + var val = valCurrent.Value; + values.Add (val); + if (val != null) { + elements.Add (ctx.Adapter.CreateObjectValue (ctx, valCurrent, new ObjectPath ("[" + currentIndex + "]"), val, ObjectValueFlags.ReadOnly)); + } else { + elements.Add (Mono.Debugging.Client.ObjectValue.CreateNullObject (this, "[" + currentIndex + "]", ctx.Adapter.GetDisplayTypeName (ctx.Adapter.GetTypeName (ctx, valCurrent.Type)), ObjectValueFlags.ReadOnly)); + } + currentIndex++; + } + } + + + public object GetElement (int idx) + { + return values [idx]; + } + + public ObjectValue[] GetChildren (ObjectPath path, int index, int count, EvaluationOptions options) + { + int idx; + if (int.TryParse (path.LastName.Replace ("[", "").Replace ("]", ""), out idx)) { + return ctx.Adapter.GetObjectValueChildren (ctx, null, values [idx], -1, -1); + } + if (index < 0) + index = 0; + if (count == 0) + return new ObjectValue[0]; + if (count == -1) + count = int.MaxValue; + Fetch (index + count); + if (count < 0 || index + count > elements.Count) { + return elements.Skip (index).ToArray (); + } else { + if (index < elements.Count) { + return elements.Skip (index).Take (System.Math.Min (count, elements.Count - index)).ToArray (); + } else { + return new ObjectValue[0]; + } + } + } + + public EvaluationResult SetValue (ObjectPath path, string value, EvaluationOptions options) + { + throw new InvalidOperationException ("Elements of IEnumerable can not be set"); + } + + public ObjectValue GetValue (ObjectPath path, EvaluationOptions options) + { + int idx; + if (int.TryParse (path.LastName.Replace ("[", "").Replace ("]", ""), out idx)) { + var element = elements [idx]; + element.Refresh (options); + return element; + } + return null; + } + + public object GetRawValue (ObjectPath path, EvaluationOptions options) + { + int idx = int.Parse (path.LastName.Replace ("[", "").Replace ("]", "")); + EvaluationContext cctx = ctx.WithOptions (options); + return cctx.Adapter.ToRawValue (cctx, new EnumerableObjectSource (this, idx), GetElement (idx)); + } + + public void SetRawValue (ObjectPath path, object value, EvaluationOptions options) + { + throw new InvalidOperationException ("Elements of IEnumerable can not be set"); + } + } + + class EnumerableObjectSource : IObjectSource + { + EnumerableSource enumerableSource; + int idx; + + public EnumerableObjectSource (EnumerableSource enumerableSource, int idx) + { + this.enumerableSource = enumerableSource; + this.idx = idx; + + } + + #region IObjectSource implementation + + public object Value { + get { + return enumerableSource.GetElement (idx); + } + set { + throw new InvalidOperationException ("Elements of IEnumerable can not be set"); + } + } + + #endregion + } +} + diff --git a/VisualPascalABCNETLinux/MonoDebugging/Evaluation/EvaluationContext.cs b/VisualPascalABCNETLinux/MonoDebugging/Evaluation/EvaluationContext.cs new file mode 100644 index 000000000..2b5a806c4 --- /dev/null +++ b/VisualPascalABCNETLinux/MonoDebugging/Evaluation/EvaluationContext.cs @@ -0,0 +1,147 @@ +// EvaluationContext.cs +// +// Author: +// Lluis Sanchez Gual +// +// Copyright (c) 2008 Novell, Inc (http://www.novell.com) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// +// + +using System; +using Mono.Debugging.Client; +using Mono.Debugging.Backend; + +namespace Mono.Debugging.Evaluation +{ + public class EvaluationContext + { + public ExpressionEvaluator Evaluator { get; set; } + public ObjectValueAdaptor Adapter { get; set; } + + public EvaluationOptions Options { + get; set; + } + + public bool CaseSensitive { + get { return Evaluator.CaseSensitive; } + } + + public virtual void WriteDebuggerError (Exception ex) + { + } + + public virtual void WriteDebuggerOutput (string message, params object[] values) + { + } + + public void WaitRuntimeInvokes () + { + } + + public void AssertTargetInvokeAllowed () + { + if (!Options.AllowTargetInvoke) + throw new ImplicitEvaluationDisabledException (); + } + + public EvaluationContext (EvaluationOptions options) + { + Options = options; + } + + public EvaluationContext Clone () + { + var clone = (EvaluationContext) MemberwiseClone (); + clone.CopyFrom (this); + return clone; + } + + public EvaluationContext WithOptions (EvaluationOptions options) + { + if (options == null || Options == options) + return this; + + var clone = Clone (); + clone.Options = options; + return clone; + } + + public virtual void CopyFrom (EvaluationContext ctx) + { + Options = ctx.Options.Clone (); + Evaluator = ctx.Evaluator; + Adapter = ctx.Adapter; + } + + ExpressionValueSource expressionValueSource; + internal ExpressionValueSource ExpressionValueSource { + get { + if (expressionValueSource == null) + expressionValueSource = new ExpressionValueSource (this); + return expressionValueSource; + } + } + + public virtual bool SupportIEnumerable { + get { + return false; + } + } + } + + class ExpressionValueSource: RemoteFrameObject, IObjectValueSource + { + readonly EvaluationContext ctx; + + public ExpressionValueSource (EvaluationContext ctx) + { + this.ctx = ctx; + Connect (); + } + + public ObjectValue[] GetChildren (ObjectPath path, int index, int count, EvaluationOptions options) + { + throw new NotImplementedException (); + } + + public EvaluationResult SetValue (ObjectPath path, string value, EvaluationOptions options) + { + throw new NotImplementedException (); + } + + public ObjectValue GetValue (ObjectPath path, EvaluationOptions options) + { + EvaluationContext c = ctx.WithOptions (options); + var vals = c.Adapter.GetExpressionValuesAsync (c, new string[] { path.LastName }); + return vals[0]; + } + + public object GetRawValue (ObjectPath path, EvaluationOptions options) + { + throw new NotImplementedException (); + } + + public void SetRawValue (ObjectPath path, object value, EvaluationOptions options) + { + throw new NotImplementedException (); + } + } +} diff --git a/VisualPascalABCNETLinux/MonoDebugging/Evaluation/ExceptionInfoSource.cs b/VisualPascalABCNETLinux/MonoDebugging/Evaluation/ExceptionInfoSource.cs new file mode 100644 index 000000000..a1552b828 --- /dev/null +++ b/VisualPascalABCNETLinux/MonoDebugging/Evaluation/ExceptionInfoSource.cs @@ -0,0 +1,244 @@ +// +// ExceptionInfoSource.cs +// +// Author: +// Lluis Sanchez Gual +// +// Copyright (c) 2010 Novell, Inc (http://www.novell.com) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +using System; +using System.Linq; +using System.Collections.Generic; +using System.Text.RegularExpressions; + +using Mono.Debugging.Client; +using Mono.Debugging.Backend; + +namespace Mono.Debugging.Evaluation +{ + public class ExceptionInfoSource + { + EvaluationContext ctx; + + public ExceptionInfoSource (EvaluationContext ctx, ValueReference exception) + { + Exception = exception; + this.ctx = ctx; + } + + public ValueReference Exception { + get; private set; + } + + public ObjectValue CreateObjectValue (bool withTimeout, EvaluationOptions options) + { + options = options.Clone (); + options.EllipsizeStrings = false; + options.AllowTargetInvoke = true; + ctx = ctx.WithOptions (options); + string type = ctx.Adapter.GetValueTypeName (ctx, Exception.ObjectValue); + + var excInstance = Exception.CreateObjectValue (withTimeout, options); + excInstance.Name = "Instance"; + + ObjectValue messageValue = null; + ObjectValue helpLinkValue = null; + var exceptionType = ctx.Adapter.GetValueType (ctx, Exception.Value); + + // Get the message + + if (withTimeout) { + messageValue = ctx.Adapter.CreateObjectValueAsync ("Message", ObjectValueFlags.None, delegate { + var mref = ctx.Adapter.GetMember (ctx, Exception, exceptionType, Exception.Value, "Message"); + if (mref != null) { + string val = (string)mref.ObjectValue; + return ObjectValue.CreatePrimitive (null, new ObjectPath ("Message"), "string", new EvaluationResult (val), ObjectValueFlags.Literal); + } + + return ObjectValue.CreateUnknown ("Message"); + }); + } else { + var mref = ctx.Adapter.GetMember (ctx, Exception, exceptionType, Exception.Value, "Message"); + if (mref != null) { + string val = (string)mref.ObjectValue; + messageValue = ObjectValue.CreatePrimitive (null, new ObjectPath ("Message"), "string", new EvaluationResult (val), ObjectValueFlags.Literal); + } + } + + if (messageValue == null) + messageValue = ObjectValue.CreateUnknown ("Message"); + + messageValue.Name = "Message"; + + // Get the help link + + if (withTimeout) { + helpLinkValue = ctx.Adapter.CreateObjectValueAsync ("HelpLink", ObjectValueFlags.None, delegate { + var mref = ctx.Adapter.GetMember (ctx, Exception, exceptionType, Exception.Value, "HelpLink"); + if (mref != null) { + string val = (string)mref.ObjectValue; + return ObjectValue.CreatePrimitive (null, new ObjectPath ("HelpLink"), "string", new EvaluationResult (val), ObjectValueFlags.Literal); + } + + return ObjectValue.CreateUnknown ("HelpLink"); + }); + } else { + var mref = ctx.Adapter.GetMember (ctx, Exception, exceptionType, Exception.Value, "HelpLink"); + if (mref != null) { + string val = (string)mref.ObjectValue; + helpLinkValue = ObjectValue.CreatePrimitive (null, new ObjectPath ("HelpLink"), "string", new EvaluationResult (val), ObjectValueFlags.Literal); + } + } + + if (helpLinkValue == null) + helpLinkValue = ObjectValue.CreateUnknown ("HelpLink"); + + helpLinkValue.Name = "HelpLink"; + + // Inner exception + + ObjectValue childExceptionValue = null; + + if (withTimeout) { + childExceptionValue = ctx.Adapter.CreateObjectValueAsync ("InnerException", ObjectValueFlags.None, delegate { + var inner = ctx.Adapter.GetMember (ctx, Exception, exceptionType, Exception.Value, "InnerException"); + if (inner != null && !ctx.Adapter.IsNull (ctx, inner.Value)) { + //Console.WriteLine ("pp got child:" + type); + var innerSource = new ExceptionInfoSource (ctx, inner); + var res = innerSource.CreateObjectValue (false, options); + return res; + } + + return ObjectValue.CreateUnknown ("InnerException"); + }); + } else { + var inner = ctx.Adapter.GetMember (ctx, Exception, exceptionType, Exception.Value, "InnerException"); + if (inner != null && !ctx.Adapter.IsNull (ctx, inner.Value)) { + //Console.WriteLine ("pp got child:" + type); + var innerSource = new ExceptionInfoSource (ctx, inner); + childExceptionValue = innerSource.CreateObjectValue (false, options); + childExceptionValue.Name = "InnerException"; + } + } + + if (childExceptionValue == null) + childExceptionValue = ObjectValue.CreateUnknown ("InnerException"); + + // Inner exceptions in case of AgregatedException + + ObjectValue childExceptionsValue = null; + ObjectEvaluatorDelegate getInnerExceptionsDelegate = delegate { + var inner = ctx.Adapter.GetMember (ctx, Exception, exceptionType, Exception.Value, "InnerExceptions"); + if (inner != null && !ctx.Adapter.IsNull (ctx, inner.Value)) { + var obj = inner.GetValue (ctx); + var objType = ctx.Adapter.GetValueType (ctx, obj); + var count = (int)ctx.Adapter.GetMember(ctx, null, obj, "Count").ObjectValue; + var childrenList = new List(); + for (int i = 0; i < count; i++) { + childrenList.Add (new ExceptionInfoSource(ctx, ctx.Adapter.GetIndexerReference(ctx, obj, objType, new object[] { ctx.Adapter.CreateValue(ctx, i) })).CreateObjectValue (withTimeout, ctx.Options)); + } + return ObjectValue.CreateObject (null, new ObjectPath("InnerExceptions"), "", "", ObjectValueFlags.None, childrenList.ToArray ()); + } + + return ObjectValue.CreateUnknown ("InnerExceptions"); + }; + if (withTimeout) { + childExceptionsValue = ctx.Adapter.CreateObjectValueAsync ("InnerExceptions", ObjectValueFlags.None, getInnerExceptionsDelegate); + } else { + childExceptionsValue = getInnerExceptionsDelegate (); + } + + if (childExceptionsValue == null) + childExceptionsValue = ObjectValue.CreateUnknown ("InnerExceptions"); + + // Stack trace + + ObjectValue stackTraceValue; + if (withTimeout) { + stackTraceValue = ctx.Adapter.CreateObjectValueAsync ("StackTrace", ObjectValueFlags.None, delegate { + var stackTrace = ctx.Adapter.GetMember (ctx, Exception, exceptionType, Exception.Value, "StackTrace"); + if (stackTrace == null) + return ObjectValue.CreateUnknown ("StackTrace"); + return GetStackTrace (stackTrace.ObjectValue as string); + }); + } else { + var stackTrace = ctx.Adapter.GetMember (ctx, Exception, exceptionType, Exception.Value, "StackTrace"); + if (stackTrace == null) + return ObjectValue.CreateUnknown ("StackTrace"); + stackTraceValue = GetStackTrace (stackTrace.ObjectValue as string); + } + + var children = new ObjectValue [] { excInstance, messageValue, helpLinkValue, stackTraceValue, childExceptionValue, childExceptionsValue }; + + return ObjectValue.CreateObject (null, new ObjectPath ("InnerException"), type, "", ObjectValueFlags.None, children); + } + + public static ObjectValue GetStackTrace (string trace) + { + if (trace == null) + return ObjectValue.CreateUnknown ("StackTrace"); + + var regex = new Regex ("at (?.*) in (?.*):(?\\d+)(,(?\\d+))?"); + var regexLine = new Regex ("at (?.*) in (?.*):line (?\\d+)(,(?\\d+))?"); + var frames = new List (); + + foreach (var sframe in trace.Split ('\n')) { + string text = sframe.Trim (' ', '\r', '\t', '"'); + string file = ""; + int column = 0; + int line = 0; + + if (text.Length == 0) + continue; + //Ignore entries like "--- End of stack trace from previous location where exception was thrown ---" + if (text.StartsWith ("---", StringComparison.Ordinal)) + continue; + + var match = regex.Match (text); + if (match.Success) { + text = match.Groups ["MethodName"].ToString (); + file = match.Groups ["FileName"].ToString (); + int.TryParse (match.Groups ["LineNumber"].ToString (), out line); + int.TryParse (match.Groups ["Column"].ToString (), out column); + } else { + match = regexLine.Match (text); + if (match.Success) { + text = match.Groups ["MethodName"].ToString (); + file = match.Groups ["FileName"].ToString (); + int.TryParse (match.Groups ["LineNumber"].ToString (), out line); + int.TryParse (match.Groups ["Column"].ToString (), out column); + } + } + + var fileVal = ObjectValue.CreatePrimitive (null, new ObjectPath ("File"), "", new EvaluationResult (file), ObjectValueFlags.None); + var lineVal = ObjectValue.CreatePrimitive (null, new ObjectPath ("Line"), "", new EvaluationResult (line.ToString ()), ObjectValueFlags.None); + var colVal = ObjectValue.CreatePrimitive (null, new ObjectPath ("Column"), "", new EvaluationResult (column.ToString ()), ObjectValueFlags.None); + var children = new ObjectValue [] { fileVal, lineVal, colVal }; + + var frame = ObjectValue.CreateObject (null, new ObjectPath (), "", new EvaluationResult (text), ObjectValueFlags.None, children); + frames.Add (frame); + } + + return ObjectValue.CreateArray (null, new ObjectPath ("StackTrace"), "", frames.Count, ObjectValueFlags.None, frames.ToArray ()); + } + } +} + diff --git a/VisualPascalABCNETLinux/MonoDebugging/Evaluation/ExpressionEvaluator.cs b/VisualPascalABCNETLinux/MonoDebugging/Evaluation/ExpressionEvaluator.cs new file mode 100644 index 000000000..47d951353 --- /dev/null +++ b/VisualPascalABCNETLinux/MonoDebugging/Evaluation/ExpressionEvaluator.cs @@ -0,0 +1,267 @@ +// IExpressionEvaluator.cs +// +// Author: +// Lluis Sanchez Gual +// +// Copyright (c) 2008 Novell, Inc (http://www.novell.com) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// +// + +using System; +using System.Text; +using System.Globalization; +using System.Collections.Generic; +using System.Runtime.Serialization; + +using Mono.Debugging.Backend; +using Mono.Debugging.Client; + +namespace Mono.Debugging.Evaluation +{ + public abstract class ExpressionEvaluator + { + public ValueReference Evaluate (EvaluationContext ctx, string expression) + { + return Evaluate (ctx, expression, null); + } + + public virtual ValueReference Evaluate (EvaluationContext ctx, string expression, object expectedType) + { + foreach (var variable in ctx.Adapter.GetLocalVariables (ctx)) + if (variable.Name == expression) + return variable; + + foreach (var parameter in ctx.Adapter.GetParameters (ctx)) + if (parameter.Name == expression) + return parameter; + + var thisVar = ctx.Adapter.GetThisReference (ctx); + if (thisVar != null) { + if (thisVar.Name == expression) + return thisVar; + foreach (var cv in thisVar.GetChildReferences (ctx.Options)) + if (cv.Name == expression) + return cv; + } + throw new EvaluatorException ("Invalid Expression: '{0}'", expression); + } + + public virtual ValidationResult ValidateExpression (EvaluationContext ctx, string expression) + { + return new ValidationResult (true, null); + } + + public string TargetObjectToString (EvaluationContext ctx, object obj) + { + var res = ctx.Adapter.TargetObjectToObject (ctx, obj); + + if (res == null) + return null; + + if (res is EvaluationResult) + return ((EvaluationResult) res).DisplayValue ?? ((EvaluationResult) res).Value; + + return res.ToString (); + } + + public EvaluationResult TargetObjectToExpression (EvaluationContext ctx, object obj) + { + return ToExpression (ctx, ctx.Adapter.TargetObjectToObject (ctx, obj)); + } + + public virtual EvaluationResult ToExpression (EvaluationContext ctx, object obj) + { + if (obj == null) + return new EvaluationResult ("null"); + + if (obj is IntPtr ptr) + return new EvaluationResult ("0x" + ptr.ToInt64 ().ToString ("x")); + + if (obj is char c) { + string str; + + if (c == '\'') + str = @"'\''"; + else if (c == '"') + str = "'\"'"; + else + str = EscapeString ("'" + c + "'"); + return new EvaluationResult (str, ((int) c) + " " + str); + } + + if (obj is string s) + return new EvaluationResult ("\"" + EscapeString (s) + "\""); + + if (obj is bool b) + return new EvaluationResult (b ? "true" : "false"); + + if (obj is decimal d) + return new EvaluationResult (d.ToString (CultureInfo.InvariantCulture)); + + if (obj is EvaluationResult result) + return result; + + if (ctx.Options.IntegerDisplayFormat == IntegerDisplayFormat.Hexadecimal) { + string fval = null; + + if (obj is sbyte) + fval = ((sbyte) obj).ToString ("x2"); + else if (obj is int) + fval = ((int) obj).ToString ("x4"); + else if (obj is short) + fval = ((short) obj).ToString ("x8"); + else if (obj is long) + fval = ((long) obj).ToString ("x16"); + else if (obj is byte) + fval = ((byte) obj).ToString ("x2"); + else if (obj is uint) + fval = ((uint) obj).ToString ("x4"); + else if (obj is ushort) + fval = ((ushort) obj).ToString ("x8"); + else if (obj is ulong) + fval = ((ulong) obj).ToString ("x16"); + + if (fval != null) + return new EvaluationResult ("0x" + fval); + } + + return new EvaluationResult (obj.ToString ()); + } + + public static string EscapeString (string text) + { + var sb = new StringBuilder (); + + for (int i = 0; i < text.Length; i++) { + char c = text[i]; + string txt; + + switch (c) { + case '"': txt = "\\\""; break; + case '\0': txt = @"\0"; break; + case '\\': txt = @"\\"; break; + case '\a': txt = @"\a"; break; + case '\b': txt = @"\b"; break; + case '\f': txt = @"\f"; break; + case '\v': txt = @"\v"; break; + case '\n': txt = @"\n"; break; + case '\r': txt = @"\r"; break; + case '\t': txt = @"\t"; break; + default: + if (char.GetUnicodeCategory (c) == UnicodeCategory.OtherNotAssigned) { + sb.AppendFormat ("\\u{0:x4}", (int) c); + } else { + sb.Append (c); + } + continue; + } + sb.Append (txt); + } + + return sb.ToString (); + } + + public virtual bool CaseSensitive { + get { return true; } + } + + public abstract string Resolve (DebuggerSession session, SourceLocation location, string expression); + + public virtual IEnumerable GetLocalVariables (EvaluationContext ctx) + { + return ctx.Adapter.GetLocalVariables (ctx); + } + + public virtual ValueReference GetThisReference (EvaluationContext ctx) + { + return ctx.Adapter.GetThisReference (ctx); + } + + public virtual IEnumerable GetParameters (EvaluationContext ctx) + { + return ctx.Adapter.GetParameters (ctx); + } + + public virtual ValueReference GetCurrentException (EvaluationContext ctx) + { + return ctx.Adapter.GetCurrentException (ctx); + } + } + + [Serializable] + public class EvaluatorException: Exception + { + protected EvaluatorException (SerializationInfo info, StreamingContext context) + : base (info, context) + { + } + + public EvaluatorException (string msg, params object[] args): base (string.Format (msg, args)) + { + } + + public EvaluatorException (Exception innerException, string msg, params object [] args) : base (string.Format (msg, args), innerException) + { + } + } + + [Serializable] + public class EvaluatorAbortedException: EvaluatorException + { + protected EvaluatorAbortedException (SerializationInfo info, StreamingContext context) + : base (info, context) + { + } + + public EvaluatorAbortedException () + : base ("Aborted.") + { + } + } + + [Serializable] + public class NotSupportedExpressionException: EvaluatorException + { + protected NotSupportedExpressionException (SerializationInfo info, StreamingContext context) + : base (info, context) + { + } + + public NotSupportedExpressionException () + : base ("Expression not supported.") + { + } + } + + [Serializable] + public class ImplicitEvaluationDisabledException: EvaluatorException + { + protected ImplicitEvaluationDisabledException (SerializationInfo info, StreamingContext context) + : base (info, context) + { + } + + public ImplicitEvaluationDisabledException () + : base ("Implicit property and method evaluation is disabled.") + { + } + } +} diff --git a/VisualPascalABCNETLinux/MonoDebugging/Evaluation/FilteredMembersSource.cs b/VisualPascalABCNETLinux/MonoDebugging/Evaluation/FilteredMembersSource.cs new file mode 100644 index 000000000..b45028bd1 --- /dev/null +++ b/VisualPascalABCNETLinux/MonoDebugging/Evaluation/FilteredMembersSource.cs @@ -0,0 +1,123 @@ +// +// FilteredMembersSource.cs +// +// Authors: Lluis Sanchez Gual +// Jeffrey Stedfast +// +// Copyright (c) 2009 Novell, Inc (http://www.novell.com) +// Copyright (c) 2012 Xamarin Inc. (http://www.xamarin.com) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Reflection; +using Mono.Debugging.Client; +using Mono.Debugging.Backend; +using System.Diagnostics; + +namespace Mono.Debugging.Evaluation +{ + public class FilteredMembersSource: RemoteFrameObject, IObjectValueSource + { + object obj; + object type; + EvaluationContext ctx; + BindingFlags bindingFlags; + IObjectSource objectSource; + + public FilteredMembersSource (EvaluationContext ctx, IObjectSource objectSource, object type, object obj, BindingFlags bindingFlags) + { + this.ctx = ctx; + this.obj = obj; + this.type = type; + this.bindingFlags = bindingFlags; + this.objectSource = objectSource; + } + + public static ObjectValue CreateNonPublicsNode (EvaluationContext ctx, IObjectSource objectSource, object type, object obj, BindingFlags bindingFlags) + { + return CreateNode (ctx, objectSource, type, obj, bindingFlags, "Non-public members"); + } + + public static ObjectValue CreateStaticsNode (EvaluationContext ctx, IObjectSource objectSource, object type, object obj, BindingFlags bindingFlags) + { + return CreateNode (ctx, objectSource, type, obj, bindingFlags, "Static members"); + } + + static ObjectValue CreateNode (EvaluationContext ctx, IObjectSource objectSource, object type, object obj, BindingFlags bindingFlags, string label) + { + FilteredMembersSource src = new FilteredMembersSource (ctx, objectSource, type, obj, bindingFlags); + src.Connect (); + ObjectValue val = ObjectValue.CreateObject (src, new ObjectPath (label), "", "", ObjectValueFlags.Group|ObjectValueFlags.ReadOnly|ObjectValueFlags.NoRefresh, null); + val.ChildSelector = ""; + return val; + } + + public ObjectValue[] GetChildren (ObjectPath path, int index, int count, EvaluationOptions options) + { + EvaluationContext cctx = ctx.WithOptions (options); + var names = new ObjectValueNameTracker (cctx); + object tdataType = null; + TypeDisplayData tdata = null; + List list = new List (); + foreach (ValueReference val in cctx.Adapter.GetMembersSorted (cctx, objectSource, type, obj, bindingFlags)) { + object decType = val.DeclaringType; + if (decType != null && decType != tdataType) { + tdataType = decType; + tdata = cctx.Adapter.GetTypeDisplayData (cctx, decType); + } + DebuggerBrowsableState state = tdata.GetMemberBrowsableState (val.Name); + if (state == DebuggerBrowsableState.Never) + continue; + ObjectValue oval = val.CreateObjectValue (options); + names.Disambiguate (val, oval); + list.Add (oval); + } + if ((bindingFlags & BindingFlags.NonPublic) == 0) { + BindingFlags newFlags = bindingFlags | BindingFlags.NonPublic; + newFlags &= ~BindingFlags.Public; + list.Add (CreateNonPublicsNode (cctx, objectSource, type, obj, newFlags)); + } + return list.ToArray (); + } + + public ObjectValue GetValue (ObjectPath path, EvaluationOptions options) + { + throw new NotSupportedException (); + } + + public EvaluationResult SetValue (ObjectPath path, string value, EvaluationOptions options) + { + throw new NotSupportedException (); + } + + public object GetRawValue (ObjectPath path, EvaluationOptions options) + { + throw new NotImplementedException (); + } + + public void SetRawValue (ObjectPath path, object value, EvaluationOptions options) + { + throw new NotImplementedException (); + } + } +} diff --git a/VisualPascalABCNETLinux/MonoDebugging/Evaluation/ICollectionAdaptor.cs b/VisualPascalABCNETLinux/MonoDebugging/Evaluation/ICollectionAdaptor.cs new file mode 100644 index 000000000..5d3eb3add --- /dev/null +++ b/VisualPascalABCNETLinux/MonoDebugging/Evaluation/ICollectionAdaptor.cs @@ -0,0 +1,42 @@ +// ICollectionAdaptor.cs +// +// Author: +// Lluis Sanchez Gual +// +// Copyright (c) 2008 Novell, Inc (http://www.novell.com) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// +// + +using System; +using Mono.Debugging.Client; + +namespace Mono.Debugging.Evaluation +{ + public interface ICollectionAdaptor + { + int[] GetLowerBounds (); + int[] GetDimensions (); + object GetElement (int[] indices); + Array GetElements (int[] indices, int count); + object ElementType { get; } + void SetElement (int[] indices, object val); + } +} diff --git a/VisualPascalABCNETLinux/MonoDebugging/Evaluation/IObjectSource.cs b/VisualPascalABCNETLinux/MonoDebugging/Evaluation/IObjectSource.cs new file mode 100644 index 000000000..6ed8487a6 --- /dev/null +++ b/VisualPascalABCNETLinux/MonoDebugging/Evaluation/IObjectSource.cs @@ -0,0 +1,35 @@ +// +// IObjectSource.cs +// +// Author: +// Lluis Sanchez Gual +// +// Copyright (c) 2009 Novell, Inc (http://www.novell.com) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +using System; + +namespace Mono.Debugging.Evaluation +{ + public interface IObjectSource + { + object Value { get; set; } + } +} diff --git a/VisualPascalABCNETLinux/MonoDebugging/Evaluation/IStringAdaptor.cs b/VisualPascalABCNETLinux/MonoDebugging/Evaluation/IStringAdaptor.cs new file mode 100644 index 000000000..cebbcc60b --- /dev/null +++ b/VisualPascalABCNETLinux/MonoDebugging/Evaluation/IStringAdaptor.cs @@ -0,0 +1,34 @@ +// +// IStringAdaptor.cs +// +// Author: Jeffrey Stedfast +// +// Copyright (c) 2012 Xamarin Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +namespace Mono.Debugging.Evaluation +{ + public interface IStringAdaptor + { + string Substring (int index, int length); + string Value { get; } + int Length { get; } + } +} diff --git a/VisualPascalABCNETLinux/MonoDebugging/Evaluation/LambdaBodyOutputVisitor.cs b/VisualPascalABCNETLinux/MonoDebugging/Evaluation/LambdaBodyOutputVisitor.cs new file mode 100644 index 000000000..f389fe959 --- /dev/null +++ b/VisualPascalABCNETLinux/MonoDebugging/Evaluation/LambdaBodyOutputVisitor.cs @@ -0,0 +1,260 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Reflection; +using Mono.Debugging.Client; + +namespace Mono.Debugging.Evaluation +{ + // Outputs lambda expression inputted on Immediate Pad. Also + // this class works for followings. + // - Check if body of lambda has not-supported expression + // - Output reserved words like `this` or `base` with generated + // identifer. + // - Collect variable references for outside the lambda + // (local variables/properties...) + /*public class LambdaBodyOutputVisitor : CSharpSyntaxWalker + { + readonly Dictionary userVariables; + readonly EvaluationContext ctx; + + Dictionary> localValues; + List definedIdentifier; + int gensymCount; + + public LambdaBodyOutputVisitor (EvaluationContext ctx, Dictionary userVariables) + { + this.ctx = ctx; + this.userVariables = userVariables; + this.localValues = new Dictionary> (); + this.definedIdentifier = new List (); + } + + public Tuple[] GetLocalValues () + { + var locals = new Tuple[localValues.Count]; + int n = 0; + foreach (var localv in localValues.Values) { + locals[n] = localv; + n++; + } + return locals; + } + + static Exception NotSupported () + { + return new NotSupportedExpressionException (); + } + + static Exception EvaluationError (string message, params object[] args) + { + return new EvaluatorException (message, args); + } + + bool IsPublicValueFlag (ObjectValueFlags flags) + { + var isField = (flags & ObjectValueFlags.Field) != 0; + var isProperty = (flags & ObjectValueFlags.Property) != 0; + var isPublic = (flags & ObjectValueFlags.Public) != 0; + + return !(isField || isProperty) || isPublic; + } + + void AssertPublicType (object type) + { + if (!ctx.Adapter.IsPublic (ctx, type)) { + var typeName = ctx.Adapter.GetDisplayTypeName (ctx, type); + throw EvaluationError ("Not Support to reference non-public type: `{0}'", typeName); + } + } + + void AssertPublicValueReference (ValueReference vr) + { + if (!(vr is NamespaceValueReference)) { + var typ = vr.Type; + AssertPublicType (typ); + } + if (!IsPublicValueFlag (vr.Flags)) { + throw EvaluationError ("Not Support to reference non-public thing: `{0}'", vr.Name); + } + } + + ValueReference Evaluate (IdentifierNameSyntax t) + { + var visitor = new NRefactoryExpressionEvaluatorVisitor (ctx, t.Identifier.ValueText, null, userVariables); + return visitor.Visit (t); + } + + ValueReference Evaluate (BaseExpressionSyntax t) + { + var visitor = new NRefactoryExpressionEvaluatorVisitor (ctx, "base", null, userVariables); + return visitor.Visit (t); + } + + ValueReference Evaluate (ThisExpressionSyntax t) + { + var visitor = new NRefactoryExpressionEvaluatorVisitor (ctx, "this", null, userVariables); + return visitor.Visit (t); + } + + string GenerateSymbol (string s) + { + var prefix = "__" + s; + var sym = prefix; + while (ExistsLocalName (sym)) { + sym = prefix + gensymCount++; + } + + return sym; + } + + string AddToLocals (string name, ValueReference vr, bool shouldRename = false) + { + if (localValues.ContainsKey (name)) + return GetLocalName (name); + + string localName; + if (shouldRename) { + localName = GenerateSymbol (name); + } else if (!ExistsLocalName (name)) { + localName = name; + } else { + throw EvaluationError ("Cannot use a variable named {0} inside lambda", name); + } + + AssertPublicValueReference (vr); + + if (!(vr is NamespaceValueReference)) { + var valu = vr?.Value; + var pair = Tuple.Create(localName, valu); + localValues.Add(name, pair); + } + return localName; + } + + string GetLocalName (string name) + { + Tuple pair; + if (localValues.TryGetValue (name, out pair)) + return pair.Item1; + return null; + } + + bool ExistsLocalName (string localName) + { + foreach (var pair in localValues.Values) { + if (pair.Item1 == localName) + return true; + } + return definedIdentifier.Contains (localName); + } + + #region IAstVisitor implementation + + public override void VisitAssignmentExpression (AssignmentExpressionSyntax node) + { + throw EvaluationError ("Not support assignment expression inside lambda"); + } + + public override void VisitBaseExpression (BaseExpressionSyntax node) + { + var basee = "base"; + var localbase = GetLocalName (basee); + if (localbase == null) { + var vr = Evaluate (node); + localbase = AddToLocals (basee, vr, true); + } + } + + public override void VisitIdentifierName (IdentifierNameSyntax node) + { + var identifier = node.Identifier.ValueText; + var localIdentifier = ""; + + if (definedIdentifier.Contains (identifier)) { + localIdentifier = identifier; + } else { + localIdentifier = GetLocalName (identifier); + if (localIdentifier == null) { + var vr = Evaluate (node); + localIdentifier = AddToLocals (identifier, vr); + } + } + } + + public override void VisitGenericName (GenericNameSyntax node) + { + foreach (var arg in node.TypeArgumentList.Arguments) { + Visit (arg); + } + } + + public override void VisitInvocationExpression (InvocationExpressionSyntax node) + { + var invocationTarget = node.Expression; + if (!(invocationTarget is IdentifierNameSyntax method)) { + Visit(invocationTarget); + return; + } + + var argc = node.ArgumentList.Arguments.Count; + var methodName = method.Identifier.ValueText; + var vref = ctx.Adapter.GetThisReference (ctx); + var vtype = ctx.Adapter.GetEnclosingType (ctx); + string accessor = null; + + var hasInstanceMethod = ctx.Adapter.HasMethodWithParamLength (ctx, vtype, methodName, BindingFlags.Instance, argc); + var hasStaticMethod = ctx.Adapter.HasMethodWithParamLength (ctx, vtype, methodName, BindingFlags.Static, argc); + var publicFlags = BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static; + var hasPublicMethod = ctx.Adapter.HasMethodWithParamLength (ctx, vtype, methodName, publicFlags, argc); + + if ((hasInstanceMethod || hasStaticMethod) && !hasPublicMethod) + throw EvaluationError ("Only support public method invocation inside lambda"); + + if (vref == null && hasStaticMethod) { + AssertPublicType (vtype); + var typeName = ctx.Adapter.GetTypeName (ctx, vtype); + accessor = ctx.Adapter.GetDisplayTypeName (typeName); + } else if (vref != null) { + AssertPublicValueReference (vref); + if (hasInstanceMethod) { + if (hasStaticMethod) { + // It's hard to determine which one is expected because + // we don't have any information of parameter types now. + throw EvaluationError ("Not supported invocation of static/instance overloaded method"); + } + accessor = GetLocalName ("this"); + if (accessor == null) + accessor = AddToLocals ("this", vref, true); + } else if (hasStaticMethod) { + var typeName = ctx.Adapter.GetTypeName (ctx, vtype); + accessor = ctx.Adapter.GetDisplayTypeName (typeName); + } + } + + for (int i = 0; i < node.ArgumentList.Arguments.Count; i++) { + Visit (node.ArgumentList.Arguments[i]); + } + } + + public override void VisitSimpleLambdaExpression (SimpleLambdaExpressionSyntax node) + { + if (node.Parameter.Modifiers.Count > 0) + throw NotSupported (); + + definedIdentifier.Add (node.Parameter.Identifier.ValueText); + Visit (node.Body); + } + + public override void VisitThisExpression (ThisExpressionSyntax node) + { + var thiss = "this"; + var localthis = GetLocalName (thiss); + if (localthis == null) { + var vr = Evaluate (node); + localthis = AddToLocals (thiss, vr, true); + } + } + #endregion + }*/ +} diff --git a/VisualPascalABCNETLinux/MonoDebugging/Evaluation/LiteralValueReference.cs b/VisualPascalABCNETLinux/MonoDebugging/Evaluation/LiteralValueReference.cs new file mode 100644 index 000000000..d4d76f0da --- /dev/null +++ b/VisualPascalABCNETLinux/MonoDebugging/Evaluation/LiteralValueReference.cs @@ -0,0 +1,180 @@ +// LiteralValueReference.cs +// +// Author: +// Lluis Sanchez Gual +// +// Copyright (c) 2008 Novell, Inc (http://www.novell.com) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// +// + +using System; +using Mono.Debugging.Client; +using Mono.Debugging.Backend; + +namespace Mono.Debugging.Evaluation +{ + public class LiteralValueReference: ValueReference + { + bool isVoidReturn; + bool objLiteral; + bool objCreated; + object objValue; + object value; + object type; + string name; + + LiteralValueReference (EvaluationContext ctx): base (ctx) + { + } + + public static LiteralValueReference CreateTargetBaseObjectLiteral (EvaluationContext ctx, string name, object value) + { + var val = new LiteralValueReference (ctx); + var type = ctx.Adapter.GetValueType (ctx, value); + val.name = name; + val.value = value; + val.type = ctx.Adapter.GetBaseType (ctx, type); + val.objCreated = true; + return val; + } + + public static LiteralValueReference CreateTargetObjectLiteral (EvaluationContext ctx, string name, object value, object type = null) + { + var val = new LiteralValueReference (ctx); + val.name = name; + val.value = value; + val.type = type ?? ctx.Adapter.GetValueType (ctx, value); + val.objCreated = true; + return val; + } + + public static LiteralValueReference CreateObjectLiteral (EvaluationContext ctx, string name, object value) + { + var val = new LiteralValueReference (ctx); + val.name = name; + val.objValue = value; + val.objLiteral = true; + return val; + } + + public static LiteralValueReference CreateVoidReturnLiteral (EvaluationContext ctx, string name) + { + var val = new LiteralValueReference (ctx); + val.value = val.objValue = new EvaluationResult ("No return value."); + val.type = typeof (EvaluationResult); + val.isVoidReturn = true; + val.objLiteral = true; + val.objCreated = true; + val.name = name; + return val; + } + + void EnsureValueAndType () + { + if (!objCreated && objLiteral) { + value = Context.Adapter.CreateValue (Context, objValue); + type = Context.Adapter.GetValueType (Context, value); + objCreated = true; + } + } + + public override object ObjectValue { + get { + return objLiteral ? objValue : base.ObjectValue; + } + } + + public override object Value { + get { + EnsureValueAndType (); + return value; + } + set { + throw new NotSupportedException (); + } + } + + public override string Name { + get { + return name; + } + } + + public override object Type { + get { + EnsureValueAndType (); + return type; + } + } + + public override ObjectValueFlags Flags { + get { + return ObjectValueFlags.Field | ObjectValueFlags.ReadOnly; + } + } + + protected override ObjectValue OnCreateObjectValue (EvaluationOptions options) + { + if (ObjectValue is EvaluationResult) { + EvaluationResult exp = (EvaluationResult) ObjectValue; + return Mono.Debugging.Client.ObjectValue.CreateObject (this, new ObjectPath (Name), "", exp, Flags, null); + } + + return base.OnCreateObjectValue (options); + } + + public override ValueReference GetChild (string name, EvaluationOptions options) + { + object obj = Value; + + if (obj == null) + return null; + + if (name [0] == '[' && Context.Adapter.IsArray (Context, obj)) { + // Parse the array indices + var tokens = name.Substring (1, name.Length - 2).Split (','); + var indices = new int [tokens.Length]; + + for (int n = 0; n < tokens.Length; n++) + indices[n] = int.Parse (tokens[n]); + + return new ArrayValueReference (Context, obj, indices); + } + + if (Context.Adapter.IsClassInstance (Context, obj)) { + // Note: This is the only difference with the default ValueReference implementation. + // We need this because the user may be requesting a base class's implementation, in + // which case 'Type' will be the BaseType instead of the actual type of the variable. + return Context.Adapter.GetMember (GetChildrenContext (options), this, Type, obj, name); + } + + return null; + } + + public override ObjectValue[] GetChildren (ObjectPath path, int index, int count, EvaluationOptions options) + { + if (isVoidReturn) + return new ObjectValue[0]; + + return base.GetChildren (path, index, count, options); + } + } +} diff --git a/VisualPascalABCNETLinux/MonoDebugging/Evaluation/NRefactoryExpressionEvaluator.cs b/VisualPascalABCNETLinux/MonoDebugging/Evaluation/NRefactoryExpressionEvaluator.cs new file mode 100644 index 000000000..3bc5ad0ce --- /dev/null +++ b/VisualPascalABCNETLinux/MonoDebugging/Evaluation/NRefactoryExpressionEvaluator.cs @@ -0,0 +1,237 @@ +// +// NRefactoryExpressionEvaluator.cs +// +// Authors: Lluis Sanchez Gual +// Jeffrey Stedfast +// +// Copyright (c) 2008 Novell, Inc (http://www.novell.com) +// Copyright (c) 2012 Xamarin Inc. (http://www.xamarin.com) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +using System; +using System.Linq; +using System.Collections.Generic; + +using Mono.Debugging.Client; + +namespace Mono.Debugging.Evaluation +{ + /*public class NRefactoryExpressionEvaluator : ExpressionEvaluator + { + readonly Dictionary userVariables = new Dictionary (); + + public override ValueReference Evaluate (EvaluationContext ctx, string expression, object expectedType) + { + expression = expression.Trim (); + + if (expression.Length > 0 && expression[0] == '?') + expression = expression.Substring (1).TrimStart (); + + if (expression.Length > 3 && expression.StartsWith ("var", StringComparison.Ordinal) && char.IsWhiteSpace (expression[3])) { + expression = expression.Substring (4).TrimStart (); + string variable = null; + + for (int n = 0; n < expression.Length; n++) { + if (!char.IsLetterOrDigit (expression[n]) && expression[n] != '_') { + variable = expression.Substring (0, n); + if (!expression.Substring (n).TrimStart ().StartsWith ("=", StringComparison.Ordinal)) + variable = null; + break; + } + + if (n == expression.Length - 1) { + variable = expression; + expression = null; + break; + } + } + + if (!string.IsNullOrEmpty (variable)) + userVariables[variable] = new UserVariableReference (ctx, variable); + + if (expression == null) + return null; + } + + expression = ReplaceExceptionTag (expression, ctx.Options.CurrentExceptionTag); + + var expr = SyntaxFactory.ParseExpression (expression); + if (expr == null) + throw new EvaluatorException ("Could not parse expression '{0}'", expression); + + var evaluator = new NRefactoryExpressionEvaluatorVisitor (ctx, expression, expectedType, userVariables); + + return evaluator.Visit (expr); + } + + public override string Resolve (DebuggerSession session, SourceLocation location, string exp) + { + return Resolve (session, location, exp, false); + } + + string Resolve (DebuggerSession session, SourceLocation location, string expression, bool tryTypeOf) + { + expression = expression.Trim (); + + if (expression.Length > 0 && expression[0] == '?') + return "?" + Resolve (session, location, expression.Substring (1).Trim ()); + + if (expression.Length > 3 && expression.StartsWith ("var", StringComparison.Ordinal) && char.IsWhiteSpace (expression[3])) + return "var " + Resolve (session, location, expression.Substring (4).Trim (' ', '\t')); + + expression = ReplaceExceptionTag (expression, session.Options.EvaluationOptions.CurrentExceptionTag); + + + var expr = SyntaxFactory.ParseExpression (expression); + if (expr == null) + return expression; + + var resolver = new NRefactoryExpressionResolverVisitor (session, location, expression); + resolver.Visit (expr); + + string resolved = resolver.GetResolvedExpression (); + if (resolved == expression && !tryTypeOf && (expr is BinaryExpressionSyntax) && IsTypeName (expression)) { + // This is a hack to be able to parse expressions such as "List". The NRefactory parser + // can parse a single type name, so a solution is to wrap it around a typeof(). We do it if + // the evaluation fails. + string res = Resolve (session, location, "typeof(" + expression + ")", true); + return res.Substring (7, res.Length - 8); + } + + return resolved; + } + + public override ValidationResult ValidateExpression (EvaluationContext ctx, string expression) + { + expression = expression.Trim (); + + if (expression.Length > 0 && expression[0] == '?') + expression = expression.Substring (1).TrimStart (); + + if (expression.Length > 3 && expression.StartsWith ("var", StringComparison.Ordinal) && char.IsWhiteSpace (expression[3])) + expression = expression.Substring (4).TrimStart (); + + expression = ReplaceExceptionTag (expression, ctx.Options.CurrentExceptionTag); + + ParseOptions options = new CSharpParseOptions (); + var expr = SyntaxFactory.ParseExpression (expression, options: options); + + if (expr.ContainsDiagnostics) + return new ValidationResult (false, expr.GetDiagnostics().First ().GetMessage()); + + return new ValidationResult (true, null); + } + + string ReplaceExceptionTag (string exp, string tag) + { + // FIXME: Don't replace inside string literals + return exp.Replace (tag, "__EXCEPTION_OBJECT__"); + } + + bool IsTypeName (string name) + { + int pos = 0; + bool res = ParseTypeName (name + "$", ref pos); + return res && pos >= name.Length; + } + + bool ParseTypeName (string name, ref int pos) + { + EatSpaces (name, ref pos); + if (!ParseName (name, ref pos)) + return false; + + EatSpaces (name, ref pos); + if (!ParseGenericArgs (name, ref pos)) + return false; + + EatSpaces (name, ref pos); + if (!ParseIndexer (name, ref pos)) + return false; + + EatSpaces (name, ref pos); + return true; + } + + void EatSpaces (string name, ref int pos) + { + while (char.IsWhiteSpace (name[pos])) + pos++; + } + + bool ParseName (string name, ref int pos) + { + if (name[0] == 'g' && pos < name.Length - 8 && name.Substring (pos, 8) == "global::") + pos += 8; + + do { + int oldp = pos; + while (char.IsLetterOrDigit (name[pos])) + pos++; + + if (oldp == pos) + return false; + + if (name[pos] != '.') + return true; + + pos++; + } while (true); + } + + bool ParseGenericArgs (string name, ref int pos) + { + if (name [pos] != '<') + return true; + + pos++; + EatSpaces (name, ref pos); + + while (true) { + if (!ParseTypeName (name, ref pos)) + return false; + + EatSpaces (name, ref pos); + char c = name [pos++]; + + if (c == '>') + return true; + + if (c == ',') + continue; + + return false; + } + } + + bool ParseIndexer (string name, ref int pos) + { + if (name [pos] != '[') + return true; + + do { + pos++; + EatSpaces (name, ref pos); + } while (name [pos] == ','); + + return name [pos++] == ']'; + } + }*/ +} diff --git a/VisualPascalABCNETLinux/MonoDebugging/Evaluation/NRefactoryExpressionEvaluatorVisitor.cs b/VisualPascalABCNETLinux/MonoDebugging/Evaluation/NRefactoryExpressionEvaluatorVisitor.cs new file mode 100644 index 000000000..9b05af3ad --- /dev/null +++ b/VisualPascalABCNETLinux/MonoDebugging/Evaluation/NRefactoryExpressionEvaluatorVisitor.cs @@ -0,0 +1,1220 @@ +// +// NRefactoryExpressionEvaluatorVisitor.cs +// +// Author: Jeffrey Stedfast +// +// Copyright (c) 2013 Xamarin Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +using System; +using System.Linq; +using System.Reflection; +using System.Collections.Generic; + +using Mono.Debugging.Client; + + +namespace Mono.Debugging.Evaluation +{ + /* + public class NRefactoryExpressionEvaluatorVisitor : CSharpSyntaxVisitor + { + readonly Dictionary userVariables; + readonly EvaluationOptions options; + readonly EvaluationContext ctx; + readonly object expectedType; + readonly string expression; + + public NRefactoryExpressionEvaluatorVisitor (EvaluationContext ctx, string expression, object expectedType, Dictionary userVariables) + { + this.ctx = ctx; + this.expression = expression; + this.expectedType = expectedType; + this.userVariables = userVariables; + this.options = ctx.Options; + } + + static Exception ParseError (string message, params object[] args) + { + return new EvaluatorException (message, args); + } + + static Exception NotSupported () + { + return new NotSupportedExpressionException (); + } + + static string ResolveTypeName (SyntaxNode type) + { + string name = type.ToString (); + if (name.StartsWith ("global::", StringComparison.Ordinal)) + name = name.Substring ("global::".Length); + + var index = name.IndexOf('<'); + if (index > -1) + name = name.Substring(0, index); + return name; + } + + static long GetInteger (object val) + { + try { + return Convert.ToInt64 (val); + } catch { + throw ParseError ("Expected integer value."); + } + } + + long ConvertToInt64 (object val) + { + if (val is IntPtr) + return ((IntPtr) val).ToInt64 (); + + if (ctx.Adapter.IsEnum (ctx, val)) { + var type = ctx.Adapter.GetType (ctx, "System.Int64"); + var result = ctx.Adapter.Cast (ctx, val, type); + + return (long) ctx.Adapter.TargetObjectToObject (ctx, result); + } + + return Convert.ToInt64 (val); + } + + static Type GetCommonOperationType (object v1, object v2) + { + if (v1 is double || v2 is double) + return typeof (double); + + if (v1 is float || v2 is float) + return typeof (double); + + return typeof (long); + } + + static Type GetCommonType (object v1, object v2) + { + var t1 = Type.GetTypeCode (v1.GetType ()); + var t2 = Type.GetTypeCode (v2.GetType ()); + if (t1 < TypeCode.Int32 && t2 < TypeCode.Int32) + return typeof (int); + + switch ((TypeCode) Math.Max ((int) t1, (int) t2)) { + case TypeCode.Byte: return typeof (byte); + case TypeCode.Decimal: return typeof (decimal); + case TypeCode.Double: return typeof (double); + case TypeCode.Int16: return typeof (short); + case TypeCode.Int32: return typeof (int); + case TypeCode.Int64: return typeof (long); + case TypeCode.SByte: return typeof (sbyte); + case TypeCode.Single: return typeof (float); + case TypeCode.UInt16: return typeof (ushort); + case TypeCode.UInt32: return typeof (uint); + case TypeCode.UInt64: return typeof (ulong); + default: throw new Exception (((TypeCode) Math.Max ((int) t1, (int) t2)).ToString ()); + } + } + + static object EvaluateOperation (SyntaxKind op, double v1, double v2) + { + switch (op) { + case SyntaxKind.AddExpression: return v1 + v2; + case SyntaxKind.DivideExpression: return v1 / v2; + case SyntaxKind.MultiplyExpression: return v1 * v2; + case SyntaxKind.SubtractExpression: return v1 - v2; + case SyntaxKind.GreaterThanExpression: return v1 > v2; + case SyntaxKind.GreaterThanOrEqualExpression: return v1 >= v2; + case SyntaxKind.LessThanExpression: return v1 < v2; + case SyntaxKind.LessThanOrEqualExpression: return v1 <= v2; + case SyntaxKind.EqualsExpression: return v1 == v2; + case SyntaxKind.NotEqualsExpression: return v1 != v2; + default: throw ParseError ("Invalid binary operator."); + } + } + + static object EvaluateOperation (SyntaxKind op, long v1, long v2) + { + switch (op) { + case SyntaxKind.AddExpression: return v1 + v2; + case SyntaxKind.BitwiseAndExpression: return v1 & v2; + case SyntaxKind.BitwiseOrExpression: return v1 | v2; + case SyntaxKind.ExclusiveOrExpression: return v1 ^ v2; + case SyntaxKind.DivideExpression: return v1 / v2; + case SyntaxKind.ModuloExpression: return v1 % v2; + case SyntaxKind.MultiplyExpression: return v1 * v2; + case SyntaxKind.LeftShiftExpression: return v1 << (int) v2; + case SyntaxKind.RightShiftExpression: return v1 >> (int) v2; + case SyntaxKind.SubtractExpression: return v1 - v2; + case SyntaxKind.GreaterThanExpression: return v1 > v2; + case SyntaxKind.GreaterThanOrEqualExpression: return v1 >= v2; + case SyntaxKind.LessThanExpression: return v1 < v2; + case SyntaxKind.LessThanOrEqualExpression: return v1 <= v2; + case SyntaxKind.EqualsExpression: return v1 == v2; + case SyntaxKind.NotEqualsExpression: return v1 != v2; + default: throw ParseError ("Invalid binary operator."); + } + } + + static bool CheckReferenceEquality (EvaluationContext ctx, object v1, object v2) + { + if (v1 == null && v2 == null) + return true; + + if (v1 == null || v2 == null) + return false; + + object objectType = ctx.Adapter.GetType (ctx, "System.Object"); + object[] argTypes = { objectType, objectType }; + object[] args = { v1, v2 }; + + object result = ctx.Adapter.RuntimeInvoke (ctx, objectType, null, "ReferenceEquals", argTypes, args); + var literal = LiteralValueReference.CreateTargetObjectLiteral (ctx, "result", result); + + return (bool) literal.ObjectValue; + } + + static bool CheckEquality (EvaluationContext ctx, bool negate, object type1, object type2, object targetVal1, object targetVal2, object val1, object val2) + { + if (val1 == null && val2 == null) + return !negate; + + if (val1 == null || val2 == null) + return negate; + + string method = negate ? "op_Inequality" : "op_Equality"; + object[] argTypes = { type1, type2 }; + object target, targetType; + object[] args; + + if (ctx.Adapter.HasMethod (ctx, type1, method, argTypes, BindingFlags.Public | BindingFlags.Static)) { + args = new [] { targetVal1, targetVal2 }; + targetType = type1; + target = null; + negate = false; + } else if (ctx.Adapter.HasMethod (ctx, type2, method, argTypes, BindingFlags.Public | BindingFlags.Static)) { + args = new [] { targetVal1, targetVal2 }; + targetType = type2; + target = null; + negate = false; + } else { + method = ctx.Adapter.IsValueType (type1) ? "Equals" : "ReferenceEquals"; + targetType = ctx.Adapter.GetType (ctx, "System.Object"); + argTypes = new [] { targetType, targetType }; + args = new [] { targetVal1, targetVal2 }; + target = null; + } + + object result = ctx.Adapter.RuntimeInvoke (ctx, targetType, target, method, argTypes, args); + var literal = LiteralValueReference.CreateTargetObjectLiteral (ctx, "result", result); + bool retval = (bool) literal.ObjectValue; + + return negate ? !retval : retval; + } + + static ValueReference EvaluateOverloadedOperator (EvaluationContext ctx, string expression, SyntaxKind op, object type1, object type2, object targetVal1, object targetVal2, object val1, object val2) + { + object[] args = new [] { targetVal1, targetVal2 }; + object[] argTypes = { type1, type2 }; + object targetType = null; + string methodName = null; + + switch (op) { + case SyntaxKind.BitwiseAndExpression: methodName = "op_BitwiseAnd"; break; + case SyntaxKind.BitwiseOrExpression: methodName = "op_BitwiseOr"; break; + case SyntaxKind.ExclusiveOrExpression: methodName = "op_ExclusiveOr"; break; + case SyntaxKind.GreaterThanExpression: methodName = "op_GreaterThan"; break; + case SyntaxKind.GreaterThanOrEqualExpression: methodName = "op_GreaterThanOrEqual"; break; + case SyntaxKind.EqualsExpression: methodName = "op_Equality"; break; + case SyntaxKind.NotEqualsExpression: methodName = "op_Inequality"; break; + case SyntaxKind.LessThanExpression: methodName = "op_LessThan"; break; + case SyntaxKind.LessThanOrEqualExpression: methodName = "op_LessThanOrEqual"; break; + case SyntaxKind.AddExpression: methodName = "op_Addition"; break; + case SyntaxKind.SubtractExpression: methodName = "op_Subtraction"; break; + case SyntaxKind.MultiplyExpression: methodName = "op_Multiply"; break; + case SyntaxKind.DivideExpression: methodName = "op_Division"; break; + case SyntaxKind.ModuloExpression: methodName = "op_Modulus"; break; + case SyntaxKind.LeftShiftExpression: methodName = "op_LeftShift"; break; + case SyntaxKind.RightShiftExpression: methodName = "op_RightShift"; break; + } + + if (methodName == null) + throw ParseError ("Invalid operands in binary operator."); + + if (ctx.Adapter.HasMethod (ctx, type1, methodName, argTypes, BindingFlags.Public | BindingFlags.Static)) { + targetType = type1; + } else if (ctx.Adapter.HasMethod (ctx, type2, methodName, argTypes, BindingFlags.Public | BindingFlags.Static)) { + targetType = type2; + } else { + throw ParseError ("Invalid operands in binary operator."); + } + + object result = ctx.Adapter.RuntimeInvoke (ctx, targetType, null, methodName, argTypes, args); + + return LiteralValueReference.CreateTargetObjectLiteral (ctx, expression, result); + } + + ValueReference EvaluateBinaryOperatorExpression (SyntaxKind op, ValueReference left, ExpressionSyntax rightExp) + { + if (op == SyntaxKind.LogicalAndExpression) { + var val = left.ObjectValue; + if (!(val is bool)) + throw ParseError ("Left operand of logical And must be a boolean."); + + if (!(bool) val) + return LiteralValueReference.CreateObjectLiteral (ctx, expression, false); + + var vr = Visit (rightExp); + if (vr == null || ctx.Adapter.GetTypeName (ctx, vr.Type) != "System.Boolean") + throw ParseError ("Right operand of logical And must be a boolean."); + + return vr; + } + + if (op == SyntaxKind.LogicalOrExpression) { + var val = left.ObjectValue; + if (!(val is bool)) + throw ParseError ("Left operand of logical Or must be a boolean."); + + if ((bool) val) + return LiteralValueReference.CreateObjectLiteral (ctx, expression, true); + + var vr = Visit (rightExp); + if (vr == null || ctx.Adapter.GetTypeName (ctx, vr.Type) != "System.Boolean") + throw ParseError ("Right operand of logical Or must be a boolean."); + + return vr; + } + + var right = Visit (rightExp); + var targetVal1 = left.Value; + var targetVal2 = right.Value; + var type1 = ctx.Adapter.GetValueType (ctx, targetVal1); + var type2 = ctx.Adapter.GetValueType (ctx, targetVal2); + var val1 = left.ObjectValue; + var val2 = right.ObjectValue; + object res = null; + + if (ctx.Adapter.IsNullableType (ctx, type1) && ctx.Adapter.NullableHasValue (ctx, type1, val1)) { + if (val2 == null) { + if (op == SyntaxKind.EqualsExpression) + return LiteralValueReference.CreateObjectLiteral (ctx, expression, false); + if (op == SyntaxKind.NotEqualsExpression) + return LiteralValueReference.CreateObjectLiteral (ctx, expression, true); + } + + ValueReference nullable = ctx.Adapter.NullableGetValue (ctx, type1, val1); + targetVal1 = nullable.Value; + val1 = nullable.ObjectValue; + type1 = nullable.Type; + } + + if (ctx.Adapter.IsNullableType (ctx, type2) && ctx.Adapter.NullableHasValue (ctx, type2, val2)) { + if (val1 == null) { + if (op == SyntaxKind.EqualsExpression) + return LiteralValueReference.CreateObjectLiteral (ctx, expression, false); + if (op == SyntaxKind.NotEqualsExpression) + return LiteralValueReference.CreateObjectLiteral (ctx, expression, true); + } + + ValueReference nullable = ctx.Adapter.NullableGetValue (ctx, type2, val2); + targetVal2 = nullable.Value; + val2 = nullable.ObjectValue; + type2 = nullable.Type; + } + + if (val1 is string || val2 is string) { + switch (op) { + case SyntaxKind.AddExpression: + if (val1 != null && val2 != null) { + if (!(val1 is string)) + val1 = ctx.Adapter.CallToString (ctx, targetVal1); + + if (!(val2 is string)) + val2 = ctx.Adapter.CallToString (ctx, targetVal2); + + res = (string) val1 + (string) val2; + } else if (val1 != null) { + res = val1.ToString (); + } else if (val2 != null) { + res = val2.ToString (); + } + + return LiteralValueReference.CreateObjectLiteral (ctx, expression, res); + case SyntaxKind.EqualsExpression: + if ((val1 == null || val1 is string) && (val2 == null || val2 is string)) + return LiteralValueReference.CreateObjectLiteral (ctx, expression, ((string) val1) == ((string) val2)); + break; + case SyntaxKind.NotEqualsExpression: + if ((val1 == null || val1 is string) && (val2 == null || val2 is string)) + return LiteralValueReference.CreateObjectLiteral (ctx, expression, ((string) val1) != ((string) val2)); + break; + } + } + + if (val1 == null || (!ctx.Adapter.IsPrimitive (ctx, targetVal1) && !ctx.Adapter.IsEnum (ctx, targetVal1))) { + switch (op) { + case SyntaxKind.EqualsExpression: + return LiteralValueReference.CreateObjectLiteral (ctx, expression, CheckEquality (ctx, false, type1, type2, targetVal1, targetVal2, val1, val2)); + case SyntaxKind.NotEqualsExpression: + return LiteralValueReference.CreateObjectLiteral (ctx, expression, CheckEquality (ctx, true, type1, type2, targetVal1, targetVal2, val1, val2)); + default: + if (val1 != null && val2 != null) + return EvaluateOverloadedOperator (ctx, expression, op, type1, type2, targetVal1, targetVal2, val1, val2); + break; + } + } + + if ((val1 is bool) && (val2 is bool)) { + switch (op) { + case SyntaxKind.ExclusiveOrExpression: + return LiteralValueReference.CreateObjectLiteral (ctx, expression, (bool) val1 ^ (bool) val2); + case SyntaxKind.EqualsExpression: + return LiteralValueReference.CreateObjectLiteral (ctx, expression, (bool) val1 == (bool) val2); + case SyntaxKind.NotEqualsExpression: + return LiteralValueReference.CreateObjectLiteral (ctx, expression, (bool) val1 != (bool) val2); + } + } + + if (val1 == null || val2 == null || (val1 is bool) || (val2 is bool)) + throw ParseError ("Invalid operands in binary operator."); + + var commonType = GetCommonOperationType (val1, val2); + + if (commonType == typeof (double)) { + double v1, v2; + + try { + v1 = Convert.ToDouble (val1); + v2 = Convert.ToDouble (val2); + } catch { + throw ParseError ("Invalid operands in binary operator."); + } + + res = EvaluateOperation (op, v1, v2); + } else { + var v1 = ConvertToInt64 (val1); + var v2 = ConvertToInt64 (val2); + + res = EvaluateOperation (op, v1, v2); + } + + if (!(res is bool) && !(res is string)) { + if (ctx.Adapter.IsEnum (ctx, targetVal1)) { + object tval = ctx.Adapter.Cast (ctx, ctx.Adapter.CreateValue (ctx, res), ctx.Adapter.GetValueType (ctx, targetVal1)); + return LiteralValueReference.CreateTargetObjectLiteral (ctx, expression, tval); + } + + if (ctx.Adapter.IsEnum (ctx, targetVal2)) { + object tval = ctx.Adapter.Cast (ctx, ctx.Adapter.CreateValue (ctx, res), ctx.Adapter.GetValueType (ctx, targetVal2)); + return LiteralValueReference.CreateTargetObjectLiteral (ctx, expression, tval); + } + + var targetType = GetCommonType (val1, val2); + + if (targetType != typeof (IntPtr)) + res = Convert.ChangeType (res, targetType); + else + res = new IntPtr ((long) res); + } + + return LiteralValueReference.CreateObjectLiteral (ctx, expression, res); + } + + static object[] UpdateDelayedTypes (object[] types, Tuple[] updates, ref bool alreadyUpdated) + { + if (alreadyUpdated || types == null || updates == null || types.Length < updates.Length || updates.Length == 0) + return types; + + for (int x = 0; x < updates.Length; x++) { + int index = updates[x].Item1; + types[index] = updates[x].Item2; + } + alreadyUpdated = true; + return types; + } + + #region IAstVisitor implementation + public override ValueReference VisitArrayCreationExpression (ArrayCreationExpressionSyntax node) + { + var type = Visit(node.Type.ElementType); + if (type == null) + throw ParseError ("Invalid type in array creation."); + + var lengths = Array.Empty(); + if (node.Type is ArrayTypeSyntax ats) { + lengths = new int[ats.RankSpecifiers[0].Sizes.Count]; + for (int i = 0; i < lengths.Length; i++) { + var arsi = ats.RankSpecifiers[0].Sizes[i]; + lengths [i] = (int)Convert.ChangeType (Visit(arsi).ObjectValue, typeof (int)); + } + } + var array = ctx.Adapter.CreateArray (ctx, type.Type, lengths); + if (node.Initializer?.Expressions.Count > 0) { + var arrayAdaptor = ctx.Adapter.CreateArrayAdaptor (ctx, array); + int index = 0; + foreach (var el in LinearElements(node.Initializer.Expressions)) { + arrayAdaptor.SetElement (new int [] { index++ }, Visit(el).Value); + } + } + return LiteralValueReference.CreateTargetObjectLiteral (ctx, expression, array); + } + + IEnumerable LinearElements (SeparatedSyntaxList elements) + { + foreach (var el in elements) { + if (el is ArrayCreationExpressionSyntax arrCre) { + foreach (var el2 in LinearElements (arrCre.Initializer.Expressions)) { + yield return el2; + } + } else if (el is InitializerExpressionSyntax ies) { + foreach (var el2 in LinearElements (ies.Expressions)) { + yield return el2; + } + } else + yield return el; + } + } + + public override ValueReference VisitAssignmentExpression (AssignmentExpressionSyntax node) + { + if (!options.AllowMethodEvaluation) + throw new ImplicitEvaluationDisabledException (); + + var left = Visit (node.Left); + + if (node.Kind () == SyntaxKind.SimpleAssignmentExpression) { + var right = Visit (node.Right); + if (left is UserVariableReference) { + left.Value = right.Value; + } else { + var castedValue = ctx.Adapter.TryCast (ctx, right.Value, left.Type); + left.Value = castedValue; + } + } else { + SyntaxKind op; + + switch (node.Kind ()) { + case SyntaxKind.AddAssignmentExpression: op = SyntaxKind.AddExpression; break; + case SyntaxKind.SubtractAssignmentExpression: op = SyntaxKind.SubtractExpression; break; + case SyntaxKind.MultiplyAssignmentExpression: op = SyntaxKind.MultiplyExpression; break; + case SyntaxKind.DivideAssignmentExpression: op = SyntaxKind.DivideExpression; break; + case SyntaxKind.ModuloAssignmentExpression: op = SyntaxKind.ModuloExpression; break; + case SyntaxKind.LeftShiftAssignmentExpression: op = SyntaxKind.LeftShiftExpression; break; + case SyntaxKind.RightShiftAssignmentExpression: op = SyntaxKind.RightShiftExpression; break; + case SyntaxKind.AndAssignmentExpression: op = SyntaxKind.BitwiseAndExpression; break; + case SyntaxKind.OrAssignmentExpression: op = SyntaxKind.BitwiseOrExpression; break; + case SyntaxKind.ExclusiveOrAssignmentExpression: op = SyntaxKind.ExclusiveOrExpression; break; + default: throw ParseError ("Invalid operator in assignment."); + } + + var result = EvaluateBinaryOperatorExpression (op, left, node.Right); + left.Value = result.Value; + } + + return left; + } + + public override ValueReference VisitBaseExpression (BaseExpressionSyntax node) + { + var self = ctx.Adapter.GetThisReference (ctx); + + if (self != null) + return LiteralValueReference.CreateTargetBaseObjectLiteral (ctx, expression, self.Value); + + throw ParseError ("'base' reference not available in static methods."); + } + + public override ValueReference VisitBinaryExpression (BinaryExpressionSyntax node) + { + if (node.IsKind (SyntaxKind.AsExpression)) { + var type = Visit (node.Right) as TypeValueReference; + if (type == null) + throw ParseError ("Invalid type in cast."); + + var val = Visit (node.Left); + var result = ctx.Adapter.TryCast (ctx, val.Value, type.Type); + + if (result == null) + return new NullValueReference (ctx, type.Type); + + return LiteralValueReference.CreateTargetObjectLiteral (ctx, expression, result, type.Type); + } + if (node.IsKind (SyntaxKind.IsExpression)) { + var type = (Visit (node.Right) as TypeValueReference)?.Type; + if (type == null) + throw ParseError ("Invalid type in 'is' expression."); + if (ctx.Adapter.IsNullableType (ctx, type)) + type = ctx.Adapter.GetGenericTypeArguments (ctx, type).Single (); + var val = Visit (node.Left).Value; + if (ctx.Adapter.IsNull (ctx, val)) + return LiteralValueReference.CreateObjectLiteral (ctx, expression, false); + var valueIsPrimitive = ctx.Adapter.IsPrimitive (ctx, val); + var typeIsPrimitive = ctx.Adapter.IsPrimitiveType (type); + if (valueIsPrimitive != typeIsPrimitive) + return LiteralValueReference.CreateObjectLiteral (ctx, expression, false); + if (typeIsPrimitive) + return LiteralValueReference.CreateObjectLiteral (ctx, expression, ctx.Adapter.GetTypeName (ctx, type) == ctx.Adapter.GetValueTypeName (ctx, val)); + return LiteralValueReference.CreateObjectLiteral (ctx, expression, ctx.Adapter.TryCast (ctx, val, type) != null); + } + + var left = this.Visit (node.Left); + + return EvaluateBinaryOperatorExpression (node.Kind (), left, node.Right); + } + + public override ValueReference VisitCastExpression (CastExpressionSyntax node) + { + var type = Visit(node.Type) as TypeValueReference; + if (type == null) + throw ParseError ("Invalid type in cast."); + + var val = Visit(node.Expression); + object result = ctx.Adapter.TryCast (ctx, val.Value, type.Type); + if (result == null) + throw ParseError ("Invalid cast."); + + return LiteralValueReference.CreateTargetObjectLiteral (ctx, expression, result, type.Type); + } + + public override ValueReference VisitCheckedExpression (CheckedExpressionSyntax node) + { + throw NotSupported (); + } + + public override ValueReference VisitConditionalExpression (ConditionalExpressionSyntax node) + { + ValueReference val = Visit(node.Condition); + if (val is TypeValueReference) + throw NotSupported (); + + if ((bool)val.ObjectValue) + return Visit (node.WhenTrue);; + + return Visit (node.WhenFalse); + } + + public override ValueReference VisitDefaultExpression (DefaultExpressionSyntax node) + { + var type = Visit(node.Type) as TypeValueReference; + if (type == null) + throw ParseError ("Invalid type in 'default' expression."); + + if (ctx.Adapter.IsClass (ctx, type.Type)) + return LiteralValueReference.CreateTargetObjectLiteral (ctx, expression, ctx.Adapter.CreateNullValue (ctx, type.Type), type.Type); + + if (ctx.Adapter.IsValueType (type.Type)) + return LiteralValueReference.CreateTargetObjectLiteral (ctx, expression, ctx.Adapter.CreateValue (ctx, type.Type, new object [0]), type.Type); + + switch (ctx.Adapter.GetTypeName (ctx, type.Type)) { + case "System.Boolean": return LiteralValueReference.CreateObjectLiteral (ctx, expression, false); + case "System.Char": return LiteralValueReference.CreateObjectLiteral (ctx, expression, '\0'); + case "System.Byte": return LiteralValueReference.CreateObjectLiteral (ctx, expression, (byte) 0); + case "System.SByte": return LiteralValueReference.CreateObjectLiteral (ctx, expression, (sbyte) 0); + case "System.Int16": return LiteralValueReference.CreateObjectLiteral (ctx, expression, (short) 0); + case "System.UInt16": return LiteralValueReference.CreateObjectLiteral (ctx, expression, (ushort) 0); + case "System.Int32": return LiteralValueReference.CreateObjectLiteral (ctx, expression, (int) 0); + case "System.UInt32": return LiteralValueReference.CreateObjectLiteral (ctx, expression, (uint) 0); + case "System.Int64": return LiteralValueReference.CreateObjectLiteral (ctx, expression, (long) 0); + case "System.UInt64": return LiteralValueReference.CreateObjectLiteral (ctx, expression, (ulong) 0); + case "System.Decimal": return LiteralValueReference.CreateObjectLiteral (ctx, expression, (decimal) 0); + case "System.Single": return LiteralValueReference.CreateObjectLiteral (ctx, expression, (float) 0); + case "System.Double": return LiteralValueReference.CreateObjectLiteral (ctx, expression, (double) 0); + default: throw new Exception ($"Unexpected type {ctx.Adapter.GetTypeName (ctx, type.Type)}"); + } + } + + public override ValueReference VisitIdentifierName (IdentifierNameSyntax node) + { + var name = node.Identifier.ValueText; + + if (name == "__EXCEPTION_OBJECT__") + return ctx.Adapter.GetCurrentException (ctx); + + // Look in user defined variables + + ValueReference userVar; + if (userVariables.TryGetValue (name, out userVar)) + return userVar; + + // Look in variables + + ValueReference var = ctx.Adapter.GetLocalVariable (ctx, name); + if (var != null) + return var; + + // Look in parameters + + var = ctx.Adapter.GetParameter (ctx, name); + if (var != null) + return var; + + // Look in instance fields and properties + + ValueReference self = ctx.Adapter.GetThisReference (ctx); + + if (self != null) { + // check for fields and properties in this instance + + // first try if current type has field or property + var = ctx.Adapter.GetMember (ctx, self, ctx.Adapter.GetEnclosingType (ctx), self.Value, name); + if (var != null) + return var; + + var = ctx.Adapter.GetMember (ctx, self, self.Type, self.Value, name); + if (var != null) + return var; + } + + // Look in static fields & properties of the enclosing type and all parent types + + object type = ctx.Adapter.GetEnclosingType (ctx); + object vtype = type; + + while (vtype != null) { + // check for static fields and properties + var = ctx.Adapter.GetMember (ctx, null, vtype, null, name); + if (var != null) + return var; + + vtype = ctx.Adapter.GetParentType (ctx, vtype); + } + + // Look in types + + vtype = ctx.Adapter.GetType (ctx, name); + if (vtype != null) + return new TypeValueReference (ctx, vtype); + + if (self == null && ctx.Adapter.HasMember (ctx, type, name, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)) { + string message = string.Format ("An object reference is required for the non-static field, method, or property '{0}.{1}'.", + ctx.Adapter.GetDisplayTypeName (ctx, type), name); + throw ParseError (message); + } + + // Assume a namespace + return new NamespaceValueReference(ctx, name); + } + + public override ValueReference VisitElementAccessExpression (ElementAccessExpressionSyntax node) + { + int n = 0; + + var target = Visit(node.Expression); + if (target is TypeValueReference) + throw NotSupported (); + + if (ctx.Adapter.IsArray (ctx, target.Value)) { + int[] indexes = new int [node.ArgumentList.Arguments.Count]; + + foreach (var arg in node.ArgumentList.Arguments) { + var index = Visit(arg); + indexes[n++] = (int) Convert.ChangeType (index.ObjectValue, typeof (int)); + } + + return new ArrayValueReference (ctx, target.Value, indexes); + } + + object[] args = new object [node.ArgumentList.Arguments.Count]; + foreach (var arg in node.ArgumentList.Arguments) + args[n++] = Visit (arg).Value; + + var indexer = ctx.Adapter.GetIndexerReference (ctx, target.Value, target.Type, args); + if (indexer == null) + throw NotSupported (); + + return indexer; + } + + string ResolveMethodName (SyntaxNode invocationExpression, out object[] typeArgs) + { + if (invocationExpression is IdentifierNameSyntax id) { + typeArgs = null; + return id.Identifier.ValueText; + } + if (invocationExpression is GenericNameSyntax gns) { + if (gns.Arity > 0) { + var args = new List (); + + foreach (var arg in gns.TypeArgumentList.Arguments) { + var type = Visit(arg); + args.Add (type.Type); + } + + typeArgs = args.ToArray (); + } else { + typeArgs = null; + } + return gns.Identifier.ValueText; + } + + typeArgs = null; + var fullName = invocationExpression.ToString(); + var lastIndexOfDot = fullName.LastIndexOf("."); + return fullName.Substring(lastIndexOfDot + 1); + } + + public override ValueReference VisitInvocationExpression (InvocationExpressionSyntax node) + { + if (!options.AllowMethodEvaluation) + throw new ImplicitEvaluationDisabledException (); + + bool invokeBaseMethod = false; + bool allArgTypesAreResolved = true; + ValueReference target = null; + string methodName; + + var types = new object [node.ArgumentList.Arguments.Count]; + var args = new object [node.ArgumentList.Arguments.Count]; + object[] typeArgs = null; + int n = 0; + + foreach (var arg in node.ArgumentList.Arguments) { + var vref = this.Visit (arg); + args[n] = vref.Value; + types[n] = ctx.Adapter.GetValueType (ctx, args[n]); + + if (ctx.Adapter.IsDelayedType (ctx, types[n])) + allArgTypesAreResolved = false; + n++; + } + object vtype = null; + Tuple[] resolvedLambdaTypes; + + if (node.Expression is MemberAccessExpressionSyntax field) { + target = Visit (field.Expression); + if (field.Expression is BaseExpressionSyntax) + invokeBaseMethod = true; + methodName = ResolveMethodName (field, out typeArgs); + } else if (node.Expression is SyntaxNode method && (method is IdentifierNameSyntax || method is GenericNameSyntax)) { + var vref = ctx.Adapter.GetThisReference (ctx); + + methodName = ResolveMethodName (method, out typeArgs); + + if (vref != null && ctx.Adapter.HasMethod (ctx, vref.Type, methodName, typeArgs, types, BindingFlags.Instance)) { + vtype = ctx.Adapter.GetEnclosingType (ctx); + // There is an instance method for 'this', although it may not have an exact signature match. Check it now. + if (ctx.Adapter.HasMethod (ctx, vref.Type, methodName, typeArgs, types, BindingFlags.Instance)) { + target = vref; + } else { + // There isn't an instance method with exact signature match. + // If there isn't a static method, then use the instance method, + // which will report the signature match error when invoked + if (!ctx.Adapter.HasMethod (ctx, vtype, methodName, typeArgs, types, BindingFlags.Static)) + target = vref; + } + } else { + if (ctx.Adapter.HasMethod (ctx, ctx.Adapter.GetEnclosingType (ctx), methodName, types, BindingFlags.Instance)) + throw new EvaluatorException ("Cannot invoke an instance method from a static method."); + target = null; + } + } else { + throw NotSupported (); + } + + if (vtype == null) + vtype = target != null ? target.Type : ctx.Adapter.GetEnclosingType (ctx); + object vtarget = (target is TypeValueReference) || target == null ? null : target.Value; + + var hasMethod = ctx.Adapter.HasMethod (ctx, vtype, methodName, typeArgs, types, BindingFlags.Instance | BindingFlags.Static, out resolvedLambdaTypes); + if (hasMethod) + types = UpdateDelayedTypes (types, resolvedLambdaTypes, ref allArgTypesAreResolved); + + if (invokeBaseMethod) { + vtype = ctx.Adapter.GetBaseType (ctx, vtype); + } else if (target != null && !hasMethod) { + // Look for LINQ extension methods... + var linq = ctx.Adapter.GetType (ctx, "System.Linq.Enumerable"); + if (linq != null) { + object[] xtypeArgs = typeArgs; + + if (xtypeArgs == null) { + // try to infer the generic type arguments from the type of the object... + object xtype = vtype; + while (xtype != null && !ctx.Adapter.IsGenericType (ctx, xtype)) + xtype = ctx.Adapter.GetBaseType (ctx, xtype); + + if (xtype != null) + xtypeArgs = ctx.Adapter.GetTypeArgs (ctx, xtype); + } + + if (xtypeArgs == null && ctx.Adapter.IsArray (ctx, vtarget)) { + xtypeArgs = new object [] { ctx.Adapter.CreateArrayAdaptor (ctx, vtarget).ElementType }; + } + + if (xtypeArgs != null) { + var xtypes = new object[types.Length + 1]; + Array.Copy (types, 0, xtypes, 1, types.Length); + xtypes[0] = vtype; + + var xargs = new object[args.Length + 1]; + Array.Copy (args, 0, xargs, 1, args.Length); + xargs[0] = vtarget; + + if (ctx.Adapter.HasMethod (ctx, linq, methodName, xtypeArgs, xtypes, BindingFlags.Static, out resolvedLambdaTypes)) { + vtarget = null; + vtype = linq; + + typeArgs = xtypeArgs; + types = UpdateDelayedTypes (xtypes, resolvedLambdaTypes, ref allArgTypesAreResolved); + args = xargs; + } + } + } + } + + if (!allArgTypesAreResolved) { + // TODO: Show detailed error message for why lambda types were not + // resolved. Major causes are: + // 1. there is no matched method + // 2. matched method exists, but the lambda body has some invalid + // expressions and does not compile + throw NotSupported (); + } + + object result = ctx.Adapter.RuntimeInvoke (ctx, vtype, vtarget, methodName, typeArgs, types, args); + if (result != null) + return LiteralValueReference.CreateTargetObjectLiteral (ctx, expression, result); + + return LiteralValueReference.CreateVoidReturnLiteral (ctx, expression); + } + + + public override ValueReference VisitParenthesizedLambdaExpression (ParenthesizedLambdaExpressionSyntax node) + { + return VisitLambdaExpression(node); + } + + public override ValueReference VisitSimpleLambdaExpression (SimpleLambdaExpressionSyntax node) + { + return VisitLambdaExpression (node); + } + + ValueReference VisitLambdaExpression (LambdaExpressionSyntax node) + { + if (node.AsyncKeyword != default) + throw NotSupported (); + + var parent = node.Parent; + while (parent != null && parent is ParenthesizedExpressionSyntax) + parent = parent.Parent; + + if (parent is InvocationExpressionSyntax || parent is CastExpressionSyntax || parent is ArgumentSyntax) { + var visitor = new LambdaBodyOutputVisitor (ctx, userVariables); + visitor.Visit (node); + var values = visitor.GetLocalValues (); + object val = ctx.Adapter.CreateDelayedLambdaValue (ctx, node.ToString (), values); + if (val != null) + return LiteralValueReference.CreateTargetObjectLiteral (ctx, expression, val); + } + + throw NotSupported (); + } + + public override ValueReference VisitMemberAccessExpression (MemberAccessExpressionSyntax node) + { + if (node.Name is GenericNameSyntax gns) { + var expr = Visit (node.Expression); + + if (expr is TypeValueReference tvr && node.Expression is GenericNameSyntax gnsOuter) { + // Thing.Done is + // Thing`1.Done`1 with type arguments System.String and System.Int32 + // Thing`1 is node.Expression + // Done`1 is node.Name + var outer = MakeGenericTypeName (gnsOuter, gnsOuter.Identifier.ValueText); + + var typeArgsOuter = GetGenericTypeArgs(gnsOuter); + var inner = MakeGenericTypeName(gns, gns.Identifier.ValueText); + var typeArgsInner = GetGenericTypeArgs(gns); + + string nestedTypeName = $"{outer}.{inner}"; + var nestedType = ctx.Adapter.GetType(ctx, nestedTypeName, typeArgsOuter.Concat(typeArgsInner).ToArray()); + return new TypeValueReference(ctx, nestedType); + + } + + var typeArgs = GetGenericTypeArgs (gns); + string typeName = $"{ResolveTypeName (node)}`{gns.TypeArgumentList.Arguments.Count}"; + + var type1 = ctx.Adapter.GetType (ctx, typeName, typeArgs); + return new TypeValueReference (ctx, type1); + } + + if (node.Name is IdentifierNameSyntax) + { + var name = ResolveTypeName(node); + var type = ctx.Adapter.GetType(ctx, name); + + if (type != null) + return new TypeValueReference(ctx, type); + } + var target = Visit (node.Expression); + + var member = target.GetChild(node.Name.Identifier.ValueText, ctx.Options); + + if (member != null) + return member; + + if (!(target is TypeValueReference)) { + if (ctx.Adapter.IsNull(ctx, target.Value)) + throw new EvaluatorException("{0} is null", target.Name); + } + throw new EvaluatorException("{0} is null", target.Name); + + } + + object [] GetGenericTypeArgs (GenericNameSyntax gns) + { + object [] typeArgs = new object [gns.TypeArgumentList.Arguments.Count]; + + for (var i = 0; i < gns.TypeArgumentList.Arguments.Count; i++) { + var typeArg = Visit (gns.TypeArgumentList.Arguments [i]); + typeArgs [i] = typeArg.Type; + } + + return typeArgs; + } + + public override ValueReference VisitLiteralExpression (LiteralExpressionSyntax node) + { + if (node.Kind() == SyntaxKind.NullLiteralExpression) + return new NullValueReference (ctx, ctx.Adapter.GetType (ctx, "System.Object")); + return base.VisitLiteralExpression (node); + } + + public override ValueReference VisitObjectCreationExpression (ObjectCreationExpressionSyntax node) + { + var type = Visit(node.Type) as TypeValueReference; + var args = new List (); + + foreach (var arg in node.ArgumentList.Arguments) { + var val = Visit(arg); + args.Add (val != null ? val.Value : null); + } + + return LiteralValueReference.CreateTargetObjectLiteral (ctx, expression, ctx.Adapter.CreateValue (ctx, type.Type, args.ToArray ())); + } + + public override ValueReference VisitParenthesizedExpression (ParenthesizedExpressionSyntax node) + { + return Visit (node.Expression); + } + + public override ValueReference VisitPredefinedType (PredefinedTypeSyntax node) + { + var type = ctx.Adapter.GetType(ctx, node.Resolve()); + return new TypeValueReference (ctx, type); + } + + public override ValueReference VisitThisExpression (ThisExpressionSyntax node) + { + var self = ctx.Adapter.GetThisReference (ctx); + + if (self == null) + throw ParseError ("'this' reference not available in the current evaluation context."); + + return self; + } + + public override ValueReference VisitTypeOfExpression (TypeOfExpressionSyntax node) + { + var name = ResolveTypeName (node.Type); + + var type = ctx.Adapter.GetType(ctx, name); + if (type == null) + throw ParseError ("Could not load type: {0}", name); + + object result = ctx.Adapter.CreateTypeObject (ctx, type); + if (result == null) + throw NotSupported (); + + return LiteralValueReference.CreateTargetObjectLiteral (ctx, name, result); + } + + public override ValueReference VisitPostfixUnaryExpression (PostfixUnaryExpressionSyntax node) + { + var vref = Visit (node.Operand); + var val = vref.ObjectValue; + object newVal; + long num; + + switch (node.Kind ()) { + case SyntaxKind.PostDecrementExpression: + if (val is decimal) { + newVal = ((decimal)val) - 1; + } else if (val is double) { + newVal = ((double)val) - 1; + } else if (val is float) { + newVal = ((float)val) - 1; + } else { + num = GetInteger (val) - 1; + newVal = Convert.ChangeType (num, val.GetType ()); + } + vref.Value = ctx.Adapter.CreateValue (ctx, newVal); + break; + case SyntaxKind.PostIncrementExpression: + if (val is decimal) { + newVal = ((decimal)val) + 1; + } else if (val is double) { + newVal = ((double)val) + 1; + } else if (val is float) { + newVal = ((float)val) + 1; + } else { + num = GetInteger (val) + 1; + newVal = Convert.ChangeType (num, val.GetType ()); + } + vref.Value = ctx.Adapter.CreateValue (ctx, newVal); + break; + default: + throw NotSupported (); + } + + return LiteralValueReference.CreateObjectLiteral (ctx, expression, val); + } + + public override ValueReference VisitPrefixUnaryExpression (PrefixUnaryExpressionSyntax node) + { + var vref = Visit (node.Operand); + var val = vref.ObjectValue; + object newVal; + long num; + + switch (node.Kind ()) { + case SyntaxKind.BitwiseNotExpression: + num = ~GetInteger (val); + val = Convert.ChangeType (num, val.GetType ()); + break; + case SyntaxKind.UnaryMinusExpression: + if (val is decimal) { + val = -(decimal)val; + } else if (val is double) { + val = -(double)val; + } else if (val is float) { + val = -(float)val; + } else { + num = -GetInteger (val); + val = Convert.ChangeType (num, val.GetType ()); + } + break; + case SyntaxKind.LogicalNotExpression: + if (!(val is bool)) + throw ParseError ("Expected boolean type in Not operator."); + + val = !(bool)val; + break; + case SyntaxKind.PreDecrementExpression: + if (val is decimal) { + val = ((decimal)val) - 1; + } else if (val is double) { + val = ((double)val) - 1; + } else if (val is float) { + val = ((float)val) - 1; + } else { + num = GetInteger (val) - 1; + val = Convert.ChangeType (num, val.GetType ()); + } + vref.Value = ctx.Adapter.CreateValue (ctx, val); + break; + case SyntaxKind.PreIncrementExpression: + if (val is decimal) { + val = ((decimal)val) + 1; + } else if (val is double) { + val = ((double)val) + 1; + } else if (val is float) { + val = ((float)val) + 1; + } else { + num = GetInteger (val) + 1; + val = Convert.ChangeType (num, val.GetType ()); + } + vref.Value = ctx.Adapter.CreateValue (ctx, val); + break; + case SyntaxKind.UnaryPlusExpression: + break; + default: + throw NotSupported (); + } + + return LiteralValueReference.CreateObjectLiteral (ctx, expression, val); + } + + public override ValueReference VisitArgument (ArgumentSyntax node) + { + return this.Visit(node.Expression); + } + public override ValueReference VisitAliasQualifiedName(AliasQualifiedNameSyntax node) + { + //return null; + return this.Visit(node.Name); + } + + public override ValueReference VisitNullableType (NullableTypeSyntax node) + { + var genericTypeArgument = new[] { Visit(node.ElementType).Type }; + var type1 = ctx.Adapter.GetType(ctx, "System.Nullable`1", genericTypeArgument); + return new TypeValueReference(ctx, type1); + } + + public override ValueReference VisitGenericName (GenericNameSyntax node) + { + object [] typeArgs = new object [node.TypeArgumentList.Arguments.Count]; + + for (var i = 0; i < node.TypeArgumentList.Arguments.Count; i++) { + var typeArg = Visit (node.TypeArgumentList.Arguments [i]); + typeArgs [i] = typeArg.Type; + } + + string typeName = node.Identifier.ValueText; + + typeName = MakeGenericTypeName(node, typeName);// $"{typeName}`{node.TypeArgumentList.Arguments.Count}"; + + + var type1 = ctx.Adapter.GetType (ctx, typeName, typeArgs); + return new TypeValueReference (ctx, type1); + } + + static string MakeGenericTypeName(GenericNameSyntax node, string typeName) + { + if (node.Parent is QualifiedNameSyntax qns && qns.Left is IdentifierNameSyntax ins) + return $"{ins.Identifier.Value}.{typeName}`{node.TypeArgumentList.Arguments.Count}"; + + if (node.Parent is QualifiedNameSyntax qns2 && qns2.Left is QualifiedNameSyntax left) + return $"{left}.{typeName}`{node.TypeArgumentList.Arguments.Count}"; + return $"{typeName}`{node.TypeArgumentList.Arguments.Count}"; +; + } + + public override ValueReference VisitQualifiedName(QualifiedNameSyntax node) + { + if(node.Right is GenericNameSyntax) { + return Visit(node.Right); + } + var type1 = ctx.Adapter.GetType(ctx, node.ToString()); + return new TypeValueReference(ctx, type1); + } + + public override ValueReference DefaultVisit (SyntaxNode node) + { + if (node is LiteralExpressionSyntax syntax) + { + return LiteralValueReference.CreateObjectLiteral(ctx, expression, syntax.Token.Value); + } + throw NotSupported(); + } + #endregion + }*/ +} \ No newline at end of file diff --git a/VisualPascalABCNETLinux/MonoDebugging/Evaluation/NRefactoryExpressionResolverVisitor.cs b/VisualPascalABCNETLinux/MonoDebugging/Evaluation/NRefactoryExpressionResolverVisitor.cs new file mode 100644 index 000000000..06d5e0d55 --- /dev/null +++ b/VisualPascalABCNETLinux/MonoDebugging/Evaluation/NRefactoryExpressionResolverVisitor.cs @@ -0,0 +1,157 @@ +// +// NRefactoryExpressionResolverVisitor.cs +// +// Author: Jeffrey Stedfast +// +// Copyright (c) 2013 Xamarin Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +using System; +using System.Text; +using System.Collections.Generic; +using Mono.Debugging.Client; + +namespace Mono.Debugging.Evaluation +{ + // FIXME: if we passed the DebuggerSession and SourceLocation into the NRefactoryExpressionEvaluatorVisitor, + // we wouldn't need to do this resolve step. + /*public class NRefactoryExpressionResolverVisitor : CSharpSyntaxWalker + { + readonly List replacements = new List (); + readonly SourceLocation location; + readonly DebuggerSession session; + readonly string expression; + string parentType; + + class Replacement + { + public string NewText; + public int Offset; + public int Length; + } + + public NRefactoryExpressionResolverVisitor (DebuggerSession session, SourceLocation location, string expression) + { + this.expression = expression.Replace ("\n", "").Replace ("\r", ""); + this.session = session; + this.location = location; + } + + internal string GetResolvedExpression () + { + if (replacements.Count == 0) + return expression; + + replacements.Sort ((r1, r2) => r1.Offset.CompareTo (r2.Offset)); + var resolved = new StringBuilder (); + int i = 0; + + foreach (var replacement in replacements) { + resolved.Append (expression, i, replacement.Offset - i); + resolved.Append (replacement.NewText); + i = replacement.Offset + replacement.Length; + } + + var last = replacements [replacements.Count - 1]; + resolved.Append (expression, last.Offset + last.Length, expression.Length - (last.Offset + last.Length)); + + return resolved.ToString (); + } + + string GenerateGenericArgs (int genericArgs) + { + if (genericArgs == 0) + return ""; + + string result = "<"; + for (int i = 0; i < genericArgs; i++) + result += "int,"; + + return result.Remove (result.Length - 1) + ">"; + } + + void ReplaceType (string name, int genericArgs, int offset, int length, bool memberType = false) + { + string type; + + if (genericArgs == 0) + type = session.ResolveIdentifierAsType (name, location); + else + type = session.ResolveIdentifierAsType (name + "`" + genericArgs, location); + + if (string.IsNullOrEmpty (type)) { + parentType = null; + } else { + if (memberType) { + type = type.Substring (type.LastIndexOf ('.') + 1); + } + + parentType = type + GenerateGenericArgs (genericArgs); + + if (type != name) { + var lengthDelta = type.Length - name.Length; + var start = offset - lengthDelta; + if (start >= 0 && expression.Substring(offset - lengthDelta, type.Length) == type) + return; + + var replacement = new Replacement { Offset = offset, Length = length, NewText = type }; + replacements.Add(replacement); + } + } + } + + void ReplaceType (SyntaxNode type) + { + int length = type.Span.Length; + int offset = type.Span.Start; + + ReplaceType (type.ToString(), 0, offset, length); + } + + public override void VisitIdentifierName (IdentifierNameSyntax node) + { + //base.VisitIdentifierName (node); + var loc = node.Identifier.GetLocation(); + int length = loc.SourceSpan.Length; + int offset = loc.SourceSpan.Start; + + ReplaceType (node.Identifier.ValueText, node.Arity, offset, length); + } + + public override void VisitSimpleBaseType (SimpleBaseTypeSyntax node) + { + ReplaceType (node); + } + + public override void VisitPredefinedType (PredefinedTypeSyntax node) + { + ReplaceType (node); + } + + public override void VisitGenericName(GenericNameSyntax node) + { + var loc = node.Identifier.GetLocation(); + int length = loc.SourceSpan.Length; + int offset = loc.SourceSpan.Start; + + ReplaceType(node.Identifier.ValueText, node.TypeArgumentList.Arguments.Count, offset, length); + } + }*/ +} \ No newline at end of file diff --git a/VisualPascalABCNETLinux/MonoDebugging/Evaluation/NRefactoryExtensions.cs b/VisualPascalABCNETLinux/MonoDebugging/Evaluation/NRefactoryExtensions.cs new file mode 100644 index 000000000..f35d8a55e --- /dev/null +++ b/VisualPascalABCNETLinux/MonoDebugging/Evaluation/NRefactoryExtensions.cs @@ -0,0 +1,213 @@ +// +// NRefactoryExtensions.cs +// +// Author: Jeffrey Stedfast +// +// Copyright (c) 2013 Xamarin Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +using System; +using System.Collections.Generic; + + +namespace Mono.Debugging.Evaluation +{ + /*public static class NRefactoryExtensions + { + #region AstType + + public static object Resolve (this TypeSyntax type, EvaluationContext ctx) + { + var args = new List (); + var name = type.Resolve (ctx, args); + + //if (name.StartsWith ("global::", StringComparison.Ordinal)) + // name = name.Substring ("global::".Length); + + if (string.IsNullOrEmpty (name)) + return null; + + if (args.Count > 0) + return ctx.Adapter.GetType (ctx, name, args.ToArray ()); + + return ctx.Adapter.GetType (ctx, name); + } + + static string Resolve (this ExpressionSyntax type, EvaluationContext ctx, List args) + { + if (type is PredefinedTypeSyntax) + return Resolve ((PredefinedTypeSyntax) type, ctx, args); + else if (type is PointerTypeSyntax) + return Resolve ((PointerTypeSyntax)type, ctx, args); + else if (type is NullableTypeSyntax) + return Resolve ((NullableTypeSyntax)type, ctx, args); + else if (type is RefTypeSyntax) + return Resolve ((RefTypeSyntax)type, ctx, args); + else if (type is QualifiedNameSyntax) + return Resolve ((QualifiedNameSyntax) type, ctx, args); + else if (type is IdentifierNameSyntax) + return Resolve ((IdentifierNameSyntax)type, ctx, args); + else if (type is GenericNameSyntax) + return Resolve ((GenericNameSyntax)type, ctx, args); + + return null; + } + + #endregion AstType + + #region ComposedType + + static string Resolve (this PointerTypeSyntax type, EvaluationContext ctx, List args) + { + return type.ElementType.Resolve (ctx, args) + "*"; + } + + static string Resolve (this RefTypeSyntax type, EvaluationContext ctx, List args) + { + return type.Type.Resolve (ctx, args); + } + + static string Resolve (this NullableTypeSyntax type, EvaluationContext ctx, List args) + { + args.Insert (0, type.ElementType.Resolve (ctx)); + return "System.Nullable`1"; + } + + #endregion ComposedType + + #region MemberType + + static string Resolve (this QualifiedNameSyntax type, EvaluationContext ctx, List args) + { + string name; + + if (!(type.Left is AliasQualifiedNameSyntax)) { + var parent = type.Left.Resolve (ctx, args); + name = parent + "." + type.Right.Identifier.ValueText; + } else { + name = type.Right.Identifier.ValueText; + } + + + if (type.Right is GenericNameSyntax genericName) { + name += "`" + genericName.TypeArgumentList.Arguments.Count; + foreach (var arg in genericName.TypeArgumentList.Arguments) { + object resolved; + + if ((resolved = arg.Resolve (ctx)) == null) + return null; + + args.Add (resolved); + } + } + + return name; + } + + #endregion MemberType + + #region PrimitiveType + + public static string Resolve (this PredefinedTypeSyntax type) + { + switch (type.Keyword.Kind()) { + case SyntaxKind.BoolKeyword: return "System.Boolean"; + case SyntaxKind.SByteKeyword: return "System.SByte"; + case SyntaxKind.ByteKeyword: return "System.Byte"; + case SyntaxKind.CharKeyword: return "System.Char"; + case SyntaxKind.ShortKeyword: return "System.Int16"; + case SyntaxKind.UShortKeyword: return "System.UInt16"; + case SyntaxKind.IntKeyword: return "System.Int32"; + case SyntaxKind.UIntKeyword: return "System.UInt32"; + case SyntaxKind.LongKeyword: return "System.Int64"; + case SyntaxKind.ULongKeyword: return "System.UInt64"; + case SyntaxKind.FloatKeyword: return "System.Single"; + case SyntaxKind.DoubleKeyword: return "System.Double"; + case SyntaxKind.DecimalKeyword: return "System.Decimal"; + case SyntaxKind.StringKeyword: return "System.String"; + case SyntaxKind.ObjectKeyword: return "System.Object"; + case SyntaxKind.VoidKeyword: return "System.Void"; + default: return null; + } + } + + public static string Resolve(string shortTypeName) + { + string longName; + switch (shortTypeName) { + case "bool": longName = "System.Boolean"; break; + case "byte": longName = "System.Byte"; break; + case "sbyte": longName = "System.SByte"; break; + case "char": longName = "System.Char"; break; + case "decimal": longName = "System.Decimal"; break; + case "double": longName = "System.Double"; break; + case "float": longName = "System.Single"; break; + case "int": longName = "System.Int32"; break; + case "uint": longName = "System.UInt32"; break; + case "nint": longName = "System.IntPtr"; break; + case "nuint": longName = "System.UIntPtr"; break; + case "long": longName = "System.Int64"; break; + case "ulong": longName = "System.UInt64"; break; + case "short": longName = "System.Int16"; break; + case "ushort": longName = "System.UInt16"; break; + case "object": longName = "System.Object"; break; + case "string": longName = "System.String"; break; + case "dynamic": longName = "System.Object"; break; + default: throw new ArgumentException($"Unknown type {shortTypeName}"); + } + return longName; + } + + static string Resolve (this PredefinedTypeSyntax type, EvaluationContext ctx, List args) + { + return Resolve (type); + } + + #endregion PrimitiveType + + #region SimpleType + + static string Resolve (this GenericNameSyntax type, EvaluationContext ctx, List args) + { + string name = type.Identifier.ValueText; + + if (type.TypeArgumentList.Arguments.Count > 0) { + name += "`" + type.TypeArgumentList.Arguments.Count; + foreach (var arg in type.TypeArgumentList.Arguments) { + object resolved; + + if ((resolved = arg.Resolve (ctx)) == null) + return null; + + args.Add (resolved); + } + } + + return type.Identifier.ValueText; + } + + static string Resolve (this IdentifierNameSyntax type, EvaluationContext ctx, List args) + { + return type.Identifier.ValueText; + } + + #endregion SimpleType + }*/ +} diff --git a/VisualPascalABCNETLinux/MonoDebugging/Evaluation/NamespaceValueReference.cs b/VisualPascalABCNETLinux/MonoDebugging/Evaluation/NamespaceValueReference.cs new file mode 100644 index 000000000..82c2ab1e2 --- /dev/null +++ b/VisualPascalABCNETLinux/MonoDebugging/Evaluation/NamespaceValueReference.cs @@ -0,0 +1,148 @@ +// NamespaceValueReference.cs +// +// Author: +// Lluis Sanchez Gual +// +// Copyright (c) 2008 Novell, Inc (http://www.novell.com) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// +// + +using System; +using System.Collections.Generic; + +using Mono.Debugging.Client; + +namespace Mono.Debugging.Evaluation +{ + public class NamespaceValueReference: ValueReference + { + readonly string namspace; + readonly string name; + + public NamespaceValueReference (EvaluationContext ctx, string name) : base (ctx) + { + namspace = name; + + int i = namspace.LastIndexOf ('.'); + if (i != -1) + this.name = namspace.Substring (i+1); + else + this.name = namspace; + } + + public override object Value { + get { + throw new NotSupportedException (); + } + set { + throw new NotSupportedException (); + } + } + + public override object Type { + get { + throw new NotSupportedException (); + } + } + + + public override object ObjectValue { + get { + throw new NotSupportedException (); + } + } + + public override string Name { + get { + return name; + } + } + + public override ObjectValueFlags Flags { + get { + return ObjectValueFlags.Namespace; + } + } + + public override ValueReference GetChild (string name, EvaluationOptions options) + { + string newNs = namspace + "." + name; + + var ctx = GetContext (options); + var type = ctx.Adapter.GetType (ctx, newNs); + + if (type != null) + return new TypeValueReference (ctx, type); + + return new NamespaceValueReference (ctx, newNs); + } + + public override ObjectValue[] GetChildren (ObjectPath path, int index, int count, EvaluationOptions options) + { + var children = new List (); + + foreach (var val in GetChildReferences (options)) + children.Add (val.CreateObjectValue (options)); + + return children.ToArray (); + } + + public override IEnumerable GetChildReferences (EvaluationOptions options) + { + // Child types + string[] childNamespaces; + string[] childTypes; + + var ctx = GetContext (options); + ctx.Adapter.GetNamespaceContents (ctx, namspace, out childNamespaces, out childTypes); + + var list = new List (); + foreach (string typeName in childTypes) { + object tt = ctx.Adapter.GetType (ctx, typeName); + if (tt != null) + list.Add (new TypeValueReference (ctx, tt)); + } + + list.Sort ((v1, v2) => string.Compare (v1.Name, v2.Name, StringComparison.CurrentCulture)); + + // Child namespaces + var listNs = new List (); + foreach (string ns in childNamespaces) + listNs.Add (new NamespaceValueReference (ctx, ns)); + + listNs.Sort ((v1, v2) => string.Compare (v1.Name, v2.Name, StringComparison.CurrentCulture)); + + list.AddRange (listNs); + + return list; + } + + protected override ObjectValue OnCreateObjectValue (EvaluationOptions options) + { + return Mono.Debugging.Client.ObjectValue.CreateObject (this, new ObjectPath (Name), "", namspace, Flags, null); + } + + public override string CallToString () + { + return namspace; + } + } +} diff --git a/VisualPascalABCNETLinux/MonoDebugging/Evaluation/NullValueReference.cs b/VisualPascalABCNETLinux/MonoDebugging/Evaluation/NullValueReference.cs new file mode 100644 index 000000000..e6f7f359f --- /dev/null +++ b/VisualPascalABCNETLinux/MonoDebugging/Evaluation/NullValueReference.cs @@ -0,0 +1,97 @@ +// NullValueReference.cs +// +// Author: +// Lluis Sanchez Gual +// +// Copyright (c) 2008 Novell, Inc (http://www.novell.com) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// +// + +using System; +using Mono.Debugging.Client; + +namespace Mono.Debugging.Evaluation +{ + public class NullValueReference: ValueReference + { + readonly object type; + object obj; + bool valueCreated; + + public NullValueReference (EvaluationContext ctx, object type) : base (ctx) + { + this.type = type; + } + + public override object Value { + get { + if (!valueCreated) { + valueCreated = true; + obj = Context.Adapter.CreateNullValue (Context, type); + } + return obj; + } + set { + throw new NotSupportedException (); + } + } + + public override object Type { + get { + return type; + } + } + + public override object ObjectValue { + get { + return null; + } + } + + public override string Name { + get { + return "null"; + } + } + + public override ObjectValueFlags Flags { + get { + return ObjectValueFlags.Literal; + } + } + + protected override ObjectValue OnCreateObjectValue (EvaluationOptions options) + { + string tn = Context.Adapter.GetTypeName (GetContext (options), Type); + return Mono.Debugging.Client.ObjectValue.CreateObject (null, new ObjectPath (Name), tn, "null", Flags, null); + } + + public override ValueReference GetChild (string name, EvaluationOptions options) + { + return null; + } + + public override ObjectValue[] GetChildren (ObjectPath path, int index, int count, EvaluationOptions options) + { + return new ObjectValue [0]; + } + } +} diff --git a/VisualPascalABCNETLinux/MonoDebugging/Evaluation/ObjectValueAdaptor.cs b/VisualPascalABCNETLinux/MonoDebugging/Evaluation/ObjectValueAdaptor.cs new file mode 100644 index 000000000..2b8c3e550 --- /dev/null +++ b/VisualPascalABCNETLinux/MonoDebugging/Evaluation/ObjectValueAdaptor.cs @@ -0,0 +1,1577 @@ +// +// ObjectValueAdaptor.cs +// +// Authors: Lluis Sanchez Gual +// Jeffrey Stedfast +// +// Copyright (c) 2008 Novell, Inc (http://www.novell.com) +// Copyright (c) 2012 Xamarin Inc. (http://www.xamarin.com) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +using System; +using System.Linq; +using System.Text; +using System.Reflection; +using System.Diagnostics; +using System.Collections.Generic; + +using Mono.Debugging.Client; +using Mono.Debugging.Backend; + +namespace Mono.Debugging.Evaluation +{ + public abstract class ObjectValueAdaptor: IDisposable + { + readonly Dictionary typeDisplayData = new Dictionary (); + + // Time to wait while evaluating before switching to async mode + public int DefaultEvaluationWaitTime { get; set; } + + public event EventHandler BusyStateChanged; + + public DebuggerSession DebuggerSession { + get { return asyncEvaluationTracker.Session; } + set { asyncEvaluationTracker.Session = value; } + } + + static readonly Dictionary CSharpTypeNames = new Dictionary (); + readonly AsyncEvaluationTracker asyncEvaluationTracker = new AsyncEvaluationTracker (); + readonly AsyncOperationManager asyncOperationManager = new AsyncOperationManager (); + + static ObjectValueAdaptor () + { + CSharpTypeNames["System.Void"] = "void"; + CSharpTypeNames["System.Object"] = "object"; + CSharpTypeNames["System.Boolean"] = "bool"; + CSharpTypeNames["System.Byte"] = "byte"; + CSharpTypeNames["System.SByte"] = "sbyte"; + CSharpTypeNames["System.Char"] = "char"; + CSharpTypeNames["System.Enum"] = "enum"; + CSharpTypeNames["System.Int16"] = "short"; + CSharpTypeNames["System.Int32"] = "int"; + CSharpTypeNames["System.Int64"] = "long"; + CSharpTypeNames["System.UInt16"] = "ushort"; + CSharpTypeNames["System.UInt32"] = "uint"; + CSharpTypeNames["System.UInt64"] = "ulong"; + CSharpTypeNames["System.Single"] = "float"; + CSharpTypeNames["System.Double"] = "double"; + CSharpTypeNames["System.Decimal"] = "decimal"; + CSharpTypeNames["System.String"] = "string"; + } + + protected ObjectValueAdaptor () + { + DefaultEvaluationWaitTime = 100; + + asyncOperationManager.BusyStateChanged += (sender, e) => OnBusyStateChanged (e); + asyncEvaluationTracker.WaitTime = DefaultEvaluationWaitTime; + } + + public void Dispose () + { + asyncEvaluationTracker.Dispose (); + asyncOperationManager.Dispose (); + } + + public ObjectValue CreateObjectValue (EvaluationContext ctx, IObjectValueSource source, ObjectPath path, object obj, ObjectValueFlags flags) + { + try { + return CreateObjectValueImpl (ctx, source, path, obj, flags); + } catch (EvaluatorAbortedException ex) { + return ObjectValue.CreateFatalError (path.LastName, ex.Message, flags); + } catch (EvaluatorException ex) { + return ObjectValue.CreateFatalError (path.LastName, ex.Message, flags); + } catch (Exception ex) { + ctx.WriteDebuggerError (ex); + return ObjectValue.CreateFatalError (path.LastName, ex.Message, flags); + } + } + + public virtual string GetDisplayTypeName (string typeName) + { + return GetDisplayTypeName (typeName.Replace ('+', '.'), 0, typeName.Length); + } + + public string GetDisplayTypeName (EvaluationContext ctx, object type) + { + return GetDisplayTypeName (GetTypeName (ctx, type)); + } + + string GetDisplayTypeName (string typeName, int startIndex, int endIndex) + { + // Note: '[' denotes the start of an array + // '`' denotes a generic type + // ',' denotes the start of the assembly name + int tokenIndex = typeName.IndexOfAny (new [] { '[', '`', ',' }, startIndex, endIndex - startIndex); + List genericArgs = null; + string array = string.Empty; + int genericEndIndex = -1; + int typeEndIndex; + + retry: + if (tokenIndex == -1) // Simple type + return GetShortTypeName (typeName.Substring (startIndex, endIndex - startIndex)); + + if (typeName[tokenIndex] == ',') // Simple type with an assembly name + return GetShortTypeName (typeName.Substring (startIndex, tokenIndex - startIndex)); + + // save the index of the end of the type name + typeEndIndex = tokenIndex; + + // decode generic args first, if this is a generic type + if (typeName[tokenIndex] == '`') { + genericEndIndex = typeName.IndexOf ('[', tokenIndex, endIndex - tokenIndex); + if (genericEndIndex == -1) { + // Mono's compiler seems to generate non-generic types with '`'s in the name + // e.g. __EventHandler`1_FileCopyEventArgs_DelegateFactory_2 + tokenIndex = typeName.IndexOfAny (new [] { '[', ',' }, tokenIndex, endIndex - tokenIndex); + goto retry; + } + + tokenIndex = genericEndIndex; + genericArgs = GetGenericArguments (typeName, ref tokenIndex, endIndex); + } + + // decode array rank info + while (tokenIndex < endIndex && typeName[tokenIndex] == '[') { + int arrayEndIndex = typeName.IndexOf (']', tokenIndex, endIndex - tokenIndex); + if (arrayEndIndex == -1) + break; + arrayEndIndex++; + array += typeName.Substring (tokenIndex, arrayEndIndex - tokenIndex); + tokenIndex = arrayEndIndex; + } + + string name = typeName.Substring (startIndex, typeEndIndex - startIndex); + + if (genericArgs == null) + return GetShortTypeName (name) + array; + + // Use the prettier name for nullable types + if (name == "System.Nullable" && genericArgs.Count == 1) + return genericArgs[0] + "?" + array; + + // Insert the generic arguments next to each type. + // for example: Foo`1+Bar`1[System.Int32,System.String] + // is converted to: Foo.Bar + var builder = new StringBuilder (name); + int i = typeEndIndex + 1; + int genericIndex = 0; + int argCount, next; + + while (i < genericEndIndex) { + // decode the argument count + argCount = 0; + while (i < genericEndIndex && char.IsDigit (typeName[i])) { + argCount = (argCount * 10) + (typeName[i] - '0'); + i++; + } + + // insert the argument types + builder.Append ('<'); + while (argCount > 0 && genericIndex < genericArgs.Count) { + builder.Append (genericArgs[genericIndex++]); + if (--argCount > 0) + builder.Append (','); + } + builder.Append ('>'); + + // Find the end of the next generic type component + if ((next = typeName.IndexOf ('`', i, genericEndIndex - i)) == -1) + next = genericEndIndex; + + // Append the next generic type component + builder.Append (typeName, i, next - i); + + i = next + 1; + } + + return builder + array; + } + + List GetGenericArguments (string typeName, ref int i, int endIndex) + { + // Get a list of the generic arguments. + // When returning, i points to the next char after the closing ']' + var genericArgs = new List (); + + i++; + + while (i < endIndex && typeName [i] != ']') { + int pend = FindTypeEnd (typeName, i, endIndex); + bool escaped = typeName [i] == '['; + + genericArgs.Add (GetDisplayTypeName (typeName, escaped ? i + 1 : i, escaped ? pend - 1 : pend)); + i = pend; + + if (i < endIndex && typeName[i] == ',') + i++; + } + + i++; + + return genericArgs; + } + + static int FindTypeEnd (string typeName, int startIndex, int endIndex) + { + int i = startIndex; + int brackets = 0; + + while (i < endIndex) { + char c = typeName[i]; + + if (c == '[') { + brackets++; + } else if (c == ']') { + if (brackets <= 0) + return i; + + brackets--; + } else if (c == ',' && brackets == 0) { + return i; + } + + i++; + } + + return i; + } + + public static string GetCSharpTypeName (string typeName) + { + int star = typeName.IndexOf ('*'); + string name, ptr, csharp; + + if (star != -1) { + name = typeName.Substring (0, star); + ptr = typeName.Substring (star); + } else { + ptr = string.Empty; + name = typeName; + } + + if (CSharpTypeNames.TryGetValue (name, out csharp)) + return csharp + ptr; + + return typeName; + } + + public virtual string GetShortTypeName (string typeName) + { + return GetCSharpTypeName (typeName); + } + + public virtual void OnBusyStateChanged (BusyStateEventArgs e) + { + EventHandler evnt = BusyStateChanged; + if (evnt != null) + evnt (this, e); + } + + public abstract ICollectionAdaptor CreateArrayAdaptor (EvaluationContext ctx, object arr); + public abstract IStringAdaptor CreateStringAdaptor (EvaluationContext ctx, object str); + + public abstract bool IsNull (EvaluationContext ctx, object val); + public abstract bool IsPrimitive (EvaluationContext ctx, object val); + public abstract bool IsPointer (EvaluationContext ctx, object val); + public abstract bool IsString (EvaluationContext ctx, object val); + public abstract bool IsArray (EvaluationContext ctx, object val); + public abstract bool IsEnum (EvaluationContext ctx, object val); + public abstract bool IsValueType (object type); + public virtual bool IsPrimitiveType (object type) { throw new NotImplementedException (); } + public abstract bool IsClass (EvaluationContext ctx, object type); + public abstract object TryCast (EvaluationContext ctx, object val, object type); + + public abstract object GetValueType (EvaluationContext ctx, object val); + public abstract string GetTypeName (EvaluationContext ctx, object type); + public abstract object[] GetTypeArgs (EvaluationContext ctx, object type); + public abstract object GetBaseType (EvaluationContext ctx, object type); + + public virtual bool IsDelayedType (EvaluationContext ctx, object type) + { + return false; + } + + public virtual bool IsGenericType (EvaluationContext ctx, object type) + { + return type != null && GetTypeName (ctx, type).IndexOf ('`') != -1; + } + + public virtual IEnumerable GetGenericTypeArguments (EvaluationContext ctx, object type) + { + yield break; + } + + public virtual bool IsNullableType (EvaluationContext ctx, object type) + { + return type != null && GetTypeName (ctx, type).StartsWith ("System.Nullable`1", StringComparison.Ordinal); + } + + public virtual bool NullableHasValue (EvaluationContext ctx, object type, object obj) + { + ValueReference hasValue = GetMember (ctx, type, obj, "HasValue"); + + return (bool) hasValue.ObjectValue; + } + + public virtual ValueReference NullableGetValue (EvaluationContext ctx, object type, object obj) + { + return GetMember (ctx, type, obj, "Value"); + } + + public virtual bool IsFlagsEnumType (EvaluationContext ctx, object type) + { + return true; + } + + public virtual bool IsSafeToInvokeMethod (EvaluationContext ctx, object method, object obj) + { + return true; + } + + public virtual IEnumerable GetEnumMembers (EvaluationContext ctx, object type) + { + object longType = GetType (ctx, "System.Int64"); + var tref = new TypeValueReference (ctx, type); + + foreach (var cr in tref.GetChildReferences (ctx.Options)) { + var c = TryCast (ctx, cr.Value, longType); + if (c == null) + continue; + + long val = (long) TargetObjectToObject (ctx, c); + var em = new EnumMember { Name = cr.Name, Value = val }; + + yield return em; + } + } + + public object GetBaseType (EvaluationContext ctx, object type, bool includeObjectClass) + { + object bt = GetBaseType (ctx, type); + string tn = bt != null ? GetTypeName (ctx, bt) : null; + + if (!includeObjectClass && bt != null && (tn == "System.Object" || tn == "System.ValueType")) + return null; + if (tn == "System.Enum") + return GetMembers (ctx, type, null, BindingFlags.GetField | BindingFlags.Instance | BindingFlags.Public).FirstOrDefault ()?.Type; + + return bt; + } + + public virtual bool IsClassInstance (EvaluationContext ctx, object val) + { + return IsClass (ctx, GetValueType (ctx, val)); + } + + public virtual bool IsExternalType (EvaluationContext ctx, object type) + { + return false; + } + + public virtual bool IsPublic (EvaluationContext ctx, object type) + { + return false; + } + + public object GetType (EvaluationContext ctx, string name) + { + return GetType (ctx, name, null); + } + + public abstract object GetType (EvaluationContext ctx, string name, object[] typeArgs); + + public virtual string GetValueTypeName (EvaluationContext ctx, object val) + { + return GetTypeName (ctx, GetValueType (ctx, val)); + } + + public virtual object CreateTypeObject (EvaluationContext ctx, object type) + { + return default (object); + } + + public virtual bool IsTypeLoaded (EvaluationContext ctx, string typeName) + { + var type = GetType (ctx, typeName); + + return type != null && IsTypeLoaded (ctx, type); + } + + public virtual bool IsTypeLoaded (EvaluationContext ctx, object type) + { + return true; + } + + public virtual object ForceLoadType (EvaluationContext ctx, string typeName) + { + var type = GetType (ctx, typeName); + + if (type == null || IsTypeLoaded (ctx, type)) + return type; + + return ForceLoadType (ctx, type) ? type : null; + } + + public virtual bool ForceLoadType (EvaluationContext ctx, object type) + { + return true; + } + + public abstract object CreateValue (EvaluationContext ctx, object value); + + public abstract object CreateValue (EvaluationContext ctx, object type, params object[] args); + + public abstract object CreateNullValue (EvaluationContext ctx, object type); + + public virtual object GetBaseValue (EvaluationContext ctx, object val) + { + return val; + } + + public virtual object CreateDelayedLambdaValue (EvaluationContext ctx, string expression, Tuple[] localVariables) + { + return null; + } + + public virtual string[] GetImportedNamespaces (EvaluationContext ctx) + { + return new string[0]; + } + + public virtual void GetNamespaceContents (EvaluationContext ctx, string namspace, out string[] childNamespaces, out string[] childTypes) + { + childTypes = childNamespaces = new string[0]; + } + + protected virtual ObjectValue CreateObjectValueImpl (EvaluationContext ctx, IObjectValueSource source, ObjectPath path, object obj, ObjectValueFlags flags) + { + object type = obj != null ? GetValueType (ctx, obj) : null; + string typeName = type != null ? GetTypeName (ctx, type) : ""; + + if (obj == null || IsNull (ctx, obj)) + return ObjectValue.CreateNullObject (source, path, GetDisplayTypeName (typeName), flags); + + if (IsPrimitive (ctx, obj) || IsEnum (ctx,obj)) + return ObjectValue.CreatePrimitive (source, path, GetDisplayTypeName (typeName), ctx.Evaluator.TargetObjectToExpression (ctx, obj), flags); + + if (IsArray (ctx, obj)) + return ObjectValue.CreateObject (source, path, GetDisplayTypeName (typeName), ctx.Evaluator.TargetObjectToExpression (ctx, obj), flags, null); + + EvaluationResult tvalue = null; + TypeDisplayData tdata = null; + string tname; + + if (IsNullableType (ctx, type)) { + if (NullableHasValue (ctx, type, obj)) { + ValueReference value = NullableGetValue (ctx, type, obj); + + tdata = GetTypeDisplayData (ctx, value.Type); + obj = value.Value; + } else { + tdata = GetTypeDisplayData (ctx, type); + tvalue = new EvaluationResult ("null"); + } + + tname = GetDisplayTypeName (typeName); + } else { + tdata = GetTypeDisplayData (ctx, type); + + if (!string.IsNullOrEmpty (tdata.TypeDisplayString) && ctx.Options.AllowDisplayStringEvaluation) { + try { + tname = EvaluateDisplayString (ctx, obj, tdata.TypeDisplayString); + } catch (MissingMemberException) { + // missing property or otherwise malformed DebuggerDisplay string + tname = GetDisplayTypeName (typeName); + } + } else { + tname = GetDisplayTypeName (typeName); + } + } + + if (tvalue == null) { + if (!string.IsNullOrEmpty (tdata.ValueDisplayString) && ctx.Options.AllowDisplayStringEvaluation) { + try { + tvalue = new EvaluationResult (EvaluateDisplayString (ctx, obj, tdata.ValueDisplayString)); + } catch (MissingMemberException) { + // missing property or otherwise malformed DebuggerDisplay string + tvalue = ctx.Evaluator.TargetObjectToExpression (ctx, obj); + } + } else { + tvalue = ctx.Evaluator.TargetObjectToExpression (ctx, obj); + } + } + + ObjectValue oval = ObjectValue.CreateObject (source, path, tname, tvalue, flags, null); + if (!string.IsNullOrEmpty (tdata.NameDisplayString) && ctx.Options.AllowDisplayStringEvaluation) { + try { + oval.Name = EvaluateDisplayString (ctx, obj, tdata.NameDisplayString); + } catch (MissingMemberException) { + // missing property or otherwise malformed DebuggerDisplay string + } + } + + return oval; + } + + public ObjectValue[] GetObjectValueChildren (EvaluationContext ctx, IObjectSource objectSource, object obj, int firstItemIndex, int count) + { + return GetObjectValueChildren (ctx, objectSource, GetValueType (ctx, obj), obj, firstItemIndex, count, true); + } + + public virtual ObjectValue[] GetObjectValueChildren (EvaluationContext ctx, IObjectSource objectSource, object type, object obj, int firstItemIndex, int count, bool dereferenceProxy) + { + if (obj is EvaluationResult) + return new ObjectValue[0]; + + if (IsArray (ctx, obj)) { + var agroup = new ArrayElementGroup (ctx, CreateArrayAdaptor (ctx, obj)); + return agroup.GetChildren (ctx.Options); + } + + if (IsPrimitive (ctx, obj)) + return new ObjectValue[0]; + + if (IsNullableType (ctx, type)) { + if (NullableHasValue (ctx, type, obj)) { + ValueReference value = NullableGetValue (ctx, type, obj); + + return GetObjectValueChildren (ctx, objectSource, value.Type, value.Value, firstItemIndex, count, dereferenceProxy); + } + + return new ObjectValue[0]; + } + + bool showRawView = false; + + // If there is a proxy, it has to show the members of the proxy + object proxy = obj; + if (dereferenceProxy) { + proxy = GetProxyObject (ctx, obj); + if (proxy != obj) { + type = GetValueType (ctx, proxy); + showRawView = true; + } + } + + TypeDisplayData tdata = GetTypeDisplayData (ctx, type); + bool groupPrivateMembers = ctx.Options.GroupPrivateMembers || IsExternalType (ctx, type); + + var values = new List (); + BindingFlags flattenFlag = ctx.Options.FlattenHierarchy ? (BindingFlags)0 : BindingFlags.DeclaredOnly; + BindingFlags nonPublicFlag = !(groupPrivateMembers || showRawView) ? BindingFlags.NonPublic : (BindingFlags) 0; + BindingFlags staticFlag = ctx.Options.GroupStaticMembers ? (BindingFlags)0 : BindingFlags.Static; + BindingFlags access = BindingFlags.Public | BindingFlags.Instance | flattenFlag | nonPublicFlag | staticFlag; + + // Load all members to a list before creating the object values, + // to avoid problems with objects being invalidated due to evaluations in the target, + var list = new List (); + list.AddRange (GetMembersSorted (ctx, objectSource, type, proxy, access)); + + // Some implementations of DebuggerProxies(showRawView==true) only have private members + if (showRawView && list.Count == 0) { + list.AddRange (GetMembersSorted (ctx, objectSource, type, proxy, access | BindingFlags.NonPublic)); + } + var names = new ObjectValueNameTracker (ctx); + object tdataType = type; + + foreach (ValueReference val in list) { + try { + object decType = val.DeclaringType; + if (decType != null && decType != tdataType) { + tdataType = decType; + tdata = GetTypeDisplayData (ctx, decType); + } + + DebuggerBrowsableState state = tdata.GetMemberBrowsableState (val.Name); + if (state == DebuggerBrowsableState.Never) + continue; + + if (state == DebuggerBrowsableState.RootHidden && dereferenceProxy) { + object ob = val.Value; + if (ob != null) { + values.Clear (); + values.AddRange (GetObjectValueChildren (ctx, val, ob, -1, -1)); + showRawView = true; + break; + } + } else { + ObjectValue oval = val.CreateObjectValue (true); + names.Disambiguate (val, oval); + values.Add (oval); + } + } catch (Exception ex) { + ctx.WriteDebuggerError (ex); + values.Add (ObjectValue.CreateError (null, new ObjectPath (val.Name), GetDisplayTypeName (GetTypeName (ctx, val.Type)), ex.Message, val.Flags)); + } + } + + if (showRawView) { + values.Add (RawViewSource.CreateRawView (ctx, objectSource, obj)); + } else { + if (IsArray (ctx, proxy)) { + var col = CreateArrayAdaptor (ctx, proxy); + var agroup = new ArrayElementGroup (ctx, col); + var val = ObjectValue.CreateObject (null, new ObjectPath ("Raw View"), "", "", ObjectValueFlags.ReadOnly, values.ToArray ()); + + values = new List (); + values.Add (val); + values.AddRange (agroup.GetChildren (ctx.Options)); + } else { + if (ctx.Options.GroupStaticMembers && HasMembers (ctx, type, proxy, BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic | flattenFlag)) { + access = BindingFlags.Static | BindingFlags.Public | flattenFlag | nonPublicFlag; + values.Add (FilteredMembersSource.CreateStaticsNode (ctx, objectSource, type, proxy, access)); + } + + if (groupPrivateMembers && HasMembers (ctx, type, proxy, BindingFlags.Instance | BindingFlags.NonPublic | flattenFlag | staticFlag)) + values.Add (FilteredMembersSource.CreateNonPublicsNode (ctx, objectSource, type, proxy, BindingFlags.Instance | BindingFlags.NonPublic | flattenFlag | staticFlag)); + + if (!ctx.Options.FlattenHierarchy) { + object baseType = GetBaseType (ctx, type, false); + if (baseType != null) + values.Insert (0, BaseTypeViewSource.CreateBaseTypeView (ctx, objectSource, baseType, proxy)); + } + + if (ctx.SupportIEnumerable) { + var iEnumerableType = GetImplementedInterfaces (ctx, type).FirstOrDefault ((interfaceType) => { + string interfaceName = GetTypeName (ctx, interfaceType); + if (interfaceName == "System.Collections.IEnumerable") + return true; + if (interfaceName == "System.Collections.Generic.IEnumerable`1") + return true; + return false; + }); + if (iEnumerableType != null) + values.Add (ObjectValue.CreatePrimitive (new EnumerableSource (proxy, iEnumerableType, ctx), new ObjectPath ("IEnumerator"), "", new EvaluationResult (""), ObjectValueFlags.ReadOnly | ObjectValueFlags.Object | ObjectValueFlags.Group | ObjectValueFlags.IEnumerable)); + } + } + } + + return values.ToArray (); + } + + public ObjectValue[] GetExpressionValuesAsync (EvaluationContext ctx, string[] expressions) + { + var values = new ObjectValue[expressions.Length]; + + for (int n = 0; n < values.Length; n++) { + string exp = expressions[n]; + + // This is a workaround to a bug in mono 2.0. That mono version fails to compile + // an anonymous method here + var edata = new ExpData (ctx, exp, this); + values[n] = asyncEvaluationTracker.Run (exp, ObjectValueFlags.Literal, edata.Run); + } + + return values; + } + + class ExpData + { + readonly ObjectValueAdaptor adaptor; + readonly EvaluationContext ctx; + readonly string exp; + + public ExpData (EvaluationContext ctx, string exp, ObjectValueAdaptor adaptor) + { + this.ctx = ctx; + this.exp = exp; + this.adaptor = adaptor; + } + + public ObjectValue Run () + { + return adaptor.GetExpressionValue (ctx, exp); + } + } + + public virtual ValueReference GetIndexerReference (EvaluationContext ctx, object target, object[] indices) + { + return null; + } + + public virtual ValueReference GetIndexerReference (EvaluationContext ctx, object target, object type, object[] indices) + { + return GetIndexerReference (ctx, target, indices); + } + + public ValueReference GetLocalVariable (EvaluationContext ctx, string name) + { + return OnGetLocalVariable (ctx, name); + } + + protected virtual ValueReference OnGetLocalVariable (EvaluationContext ctx, string name) + { + ValueReference best = null; + foreach (ValueReference var in GetLocalVariables (ctx)) { + if (var.Name == name) + return var; + if (!ctx.Evaluator.CaseSensitive && var.Name.Equals (name, StringComparison.CurrentCultureIgnoreCase)) + best = var; + } + return best; + } + + public virtual ValueReference GetParameter (EvaluationContext ctx, string name) + { + return OnGetParameter (ctx, name); + } + + protected virtual ValueReference OnGetParameter (EvaluationContext ctx, string name) + { + ValueReference best = null; + foreach (ValueReference var in GetParameters (ctx)) { + if (var.Name == name) + return var; + if (!ctx.Evaluator.CaseSensitive && var.Name.Equals (name, StringComparison.CurrentCultureIgnoreCase)) + best = var; + } + return best; + } + + public IEnumerable GetLocalVariables (EvaluationContext ctx) + { + return OnGetLocalVariables (ctx); + } + + public ValueReference GetThisReference (EvaluationContext ctx) + { + return OnGetThisReference (ctx); + } + + public IEnumerable GetParameters (EvaluationContext ctx) + { + return OnGetParameters (ctx); + } + + protected virtual IEnumerable OnGetLocalVariables (EvaluationContext ctx) + { + yield break; + } + + protected virtual IEnumerable OnGetParameters (EvaluationContext ctx) + { + yield break; + } + + protected virtual ValueReference OnGetThisReference (EvaluationContext ctx) + { + return null; + } + + public virtual ValueReference GetCurrentException (EvaluationContext ctx) + { + return null; + } + + public virtual object GetEnclosingType (EvaluationContext ctx) + { + return null; + } + + protected virtual CompletionData GetMemberCompletionData (EvaluationContext ctx, ValueReference vr) + { + var data = new CompletionData (); + + foreach (var cv in vr.GetChildReferences (ctx.Options)) + data.Items.Add (new CompletionItem (cv.Name, cv.Flags)); + + data.ExpressionLength = 0; + + return data; + } + + public virtual CompletionData GetExpressionCompletionData (EvaluationContext ctx, string expr) + { + if (expr == null) + return null; + + int dot = expr.LastIndexOf ('.'); + + if (dot != -1) { + try { + var vr = ctx.Evaluator.Evaluate (ctx, expr.Substring (0, dot), null); + if (vr != null) { + var completionData = GetMemberCompletionData (ctx, vr); + completionData.ExpressionLength = expr.Length - dot - 1; + return completionData; + } + + // FIXME: handle types and namespaces... + } catch (EvaluatorException) { + } catch (Exception ex) { + ctx.WriteDebuggerError (ex); + } + + return null; + } + + bool lastWastLetter = false; + int i = expr.Length - 1; + + while (i >= 0) { + char c = expr[i--]; + if (!char.IsLetterOrDigit (c) && c != '_') + break; + + lastWastLetter = !char.IsDigit (c); + } + + if (lastWastLetter || expr.Length == 0) { + var data = new CompletionData (); + data.ExpressionLength = expr.Length - (i + 1); + + // Local variables + + foreach (var vc in GetLocalVariables (ctx)) { + data.Items.Add (new CompletionItem (vc.Name, vc.Flags)); + } + + // Parameters + + foreach (var vc in GetParameters (ctx)) { + data.Items.Add (new CompletionItem (vc.Name, vc.Flags)); + } + + // Members + + ValueReference thisobj = GetThisReference (ctx); + + if (thisobj != null) + data.Items.Add (new CompletionItem ("this", ObjectValueFlags.Field | ObjectValueFlags.ReadOnly)); + + object type = GetEnclosingType (ctx); + + foreach (var vc in GetMembers (ctx, null, type, thisobj != null ? thisobj.Value : null)) { + data.Items.Add (new CompletionItem (vc.Name, vc.Flags)); + } + + if (data.Items.Count > 0) + return data; + } + + return null; + } + + public IEnumerable GetMembers (EvaluationContext ctx, IObjectSource objectSource, object t, object co) + { + foreach (ValueReference val in GetMembers (ctx, objectSource, t, co, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static)) { + val.ParentSource = objectSource; + yield return val; + } + } + + public ValueReference GetMember (EvaluationContext ctx, IObjectSource objectSource, object co, string name) + { + return GetMember (ctx, objectSource, GetValueType (ctx, co), co, name); + } + + protected virtual ValueReference OnGetMember (EvaluationContext ctx, IObjectSource objectSource, object t, object co, string name) + { + return GetMember (ctx, t, co, name); + } + + public ValueReference GetMember (EvaluationContext ctx, IObjectSource objectSource, object t, object co, string name) + { + ValueReference m = OnGetMember (ctx, objectSource, t, co, name); + if (m != null) + m.ParentSource = objectSource; + return m; + } + + protected virtual ValueReference GetMember (EvaluationContext ctx, object t, object co, string name) + { + ValueReference best = null; + foreach (ValueReference var in GetMembers (ctx, t, co, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static)) { + if (var.Name == name) + return var; + if (!ctx.Evaluator.CaseSensitive && var.Name.Equals (name, StringComparison.CurrentCultureIgnoreCase)) + best = var; + } + return best; + } + + internal IEnumerable GetMembersSorted (EvaluationContext ctx, IObjectSource objectSource, object t, object co) + { + return GetMembersSorted (ctx, objectSource, t, co, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static); + } + + internal IEnumerable GetMembersSorted (EvaluationContext ctx, IObjectSource objectSource, object t, object co, BindingFlags bindingFlags) + { + var list = new List (); + + foreach (var vr in GetMembers (ctx, objectSource, t, co, bindingFlags)) { + vr.ParentSource = objectSource; + list.Add (vr); + } + + list.Sort ((v1, v2) => string.Compare (v1.Name, v2.Name, StringComparison.Ordinal)); + + return list; + } + + public bool HasMembers (EvaluationContext ctx, object t, object co, BindingFlags bindingFlags) + { + return GetMembers (ctx, t, co, bindingFlags).Any (); + } + + public bool HasMember (EvaluationContext ctx, object type, string memberName) + { + return HasMember (ctx, type, memberName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static); + } + + public abstract bool HasMember (EvaluationContext ctx, object type, string memberName, BindingFlags bindingFlags); + + /// + /// Returns all members of a type. The following binding flags have to be honored: + /// BindingFlags.Static, BindingFlags.Instance, BindingFlags.Public, BindingFlags.NonPublic, BindingFlags.DeclareOnly + /// + protected abstract IEnumerable GetMembers (EvaluationContext ctx, object t, object co, BindingFlags bindingFlags); + + /// + /// Returns all members of a type. The following binding flags have to be honored: + /// BindingFlags.Static, BindingFlags.Instance, BindingFlags.Public, BindingFlags.NonPublic, BindingFlags.DeclareOnly + /// + protected virtual IEnumerable GetMembers (EvaluationContext ctx, IObjectSource objectSource, object t, object co, BindingFlags bindingFlags) + { + return GetMembers (ctx, t, co, bindingFlags); + } + + public virtual IEnumerable GetNestedTypes (EvaluationContext ctx, object type) + { + yield break; + } + + public virtual IEnumerable GetImplementedInterfaces (EvaluationContext ctx, object type) + { + yield break; + } + + public virtual object GetParentType (EvaluationContext ctx, object type) + { + var tt = type as Type; + + if (tt != null) + return tt.DeclaringType; + + var name = GetTypeName (ctx, type); + int plus = name.LastIndexOf ('+'); + + return plus != -1 ? GetType (ctx, name.Substring (0, plus)) : null; + } + + public virtual object CreateArray (EvaluationContext ctx, object type, object[] values) + { + var arrType = GetType (ctx, "System.Collections.ArrayList"); + var arrayList = CreateValue (ctx, arrType, new object[0]); + object[] objTypes = { GetType (ctx, "System.Object") }; + + foreach (object value in values) + RuntimeInvoke (ctx, arrType, arrayList, "Add", objTypes, new [] { value }); + + var typof = CreateTypeObject (ctx, type); + objTypes = new [] { GetType (ctx, "System.Type") }; + + return RuntimeInvoke (ctx, arrType, arrayList, "ToArray", objTypes, new [] { typof }); + } + + public virtual object CreateArray (EvaluationContext ctx, object type, int [] lengths) + { + if (lengths.Length > 3) { + throw new NotSupportedException ("Arrays with more than 3 demensions are not supported."); + } + var arrType = GetType (ctx, "System.Array"); + var intType = GetType (ctx, "System.Int32"); + var typeType = GetType (ctx, "System.Type"); + var arguments = new object [lengths.Length + 1]; + var argTypes = new object [lengths.Length + 1]; + arguments [0] = CreateTypeObject (ctx, type); + argTypes [0] = typeType; + for (int i = 0; i < lengths.Length; i++) { + arguments [i + 1] = FromRawValue (ctx, lengths [i]); + argTypes [i + 1] = intType; + } + + return RuntimeInvoke (ctx, arrType, null, "CreateInstance", argTypes, arguments); + } + + public virtual object ToRawValue (EvaluationContext ctx, IObjectSource source, object obj) + { + if (IsEnum (ctx, obj)) { + var longType = GetType (ctx, "System.Int64"); + var c = Cast (ctx, obj, longType); + + return TargetObjectToObject (ctx, c); + } + + if (ctx.Options.ChunkRawStrings && IsString (ctx, obj)) { + var adaptor = CreateStringAdaptor (ctx, obj); + return new RawValueString (new RemoteRawValueString (adaptor, obj)); + } + + if (IsPrimitive (ctx, obj)) + return TargetObjectToObject (ctx, obj); + + if (IsArray (ctx, obj)) { + var adaptor = CreateArrayAdaptor (ctx, obj); + return new RawValueArray (new RemoteRawValueArray (ctx, source, adaptor, obj)); + } + + return new RawValue (new RemoteRawValue (ctx, source, obj)); + } + + public virtual object FromRawValue (EvaluationContext ctx, object obj) + { + var rawValue = obj as RawValue; + if (rawValue != null) { + var val = rawValue.Source as RemoteRawValue; + if (val == null) + throw new InvalidOperationException ("Unknown RawValue source: " + rawValue.Source); + + return val.TargetObject; + } + + var rawArray = obj as RawValueArray; + if (rawArray != null) { + var val = rawArray.Source as RemoteRawValueArray; + if (val == null) + throw new InvalidOperationException ("Unknown RawValueArray source: " + rawArray.Source); + + return val.TargetObject; + } + + var rawString = obj as RawValueString; + if (rawString != null) { + var val = rawString.Source as RemoteRawValueString; + if (val == null) + throw new InvalidOperationException ("Unknown RawValueString source: " + rawString.Source); + + return val.TargetObject; + } + + var array = obj as Array; + if (array != null) { + if (obj.GetType ().GetElementType () == typeof (RawValue)) + throw new NotSupportedException (); + + var elemType = GetType (ctx, obj.GetType ().GetElementType ().FullName); + if (elemType == null) + throw new EvaluatorException ("Unknown target type: {0}", obj.GetType ().GetElementType ().FullName); + + var values = new object [array.Length]; + for (int n = 0; n < values.Length; n++) + values[n] = FromRawValue (ctx, array.GetValue (n)); + + return CreateArray (ctx, elemType, values); + } + + return CreateValue (ctx, obj); + } + + public virtual object TargetObjectToObject (EvaluationContext ctx, object obj) + { + if (IsNull (ctx, obj)) + return null; + + if (IsArray (ctx, obj)) { + ICollectionAdaptor adaptor = CreateArrayAdaptor (ctx, obj); + string ename = GetDisplayTypeName (GetTypeName (ctx, adaptor.ElementType)); + int[] dims = adaptor.GetDimensions (); + var tn = new StringBuilder ("["); + + for (int n = 0; n < dims.Length; n++) { + if (n > 0) + tn.Append (','); + tn.Append (dims[n]); + } + + tn.Append ("]"); + + int i = ename.LastIndexOf ('>'); + if (i == -1) + i = 0; + + i = ename.IndexOf ('[', i); + + if (i != -1) + return new EvaluationResult ("{" + ename.Substring (0, i) + tn + ename.Substring (i) + "}"); + + return new EvaluationResult ("{" + ename + tn + "}"); + } + + object type = GetValueType (ctx, obj); + string typeName = GetTypeName (ctx, type); + if (IsEnum (ctx, obj)) { + object longType = GetType (ctx, "System.Int64"); + object c = Cast (ctx, obj, longType); + long val = (long) TargetObjectToObject (ctx, c); + long rest = val; + string composed = string.Empty; + string composedDisplay = string.Empty; + + foreach (var em in GetEnumMembers (ctx, type)) { + if (em.Value == val) + return new EvaluationResult (typeName + "." + em.Name, em.Name); + + if (em.Value != 0 && (rest & em.Value) == em.Value) { + rest &= ~em.Value; + if (composed.Length > 0) { + composed += " | "; + composedDisplay += " | "; + } + composed += typeName + "." + em.Name; + composedDisplay += em.Name; + } + } + + if (IsFlagsEnumType (ctx, type) && rest == 0 && composed.Length > 0) + return new EvaluationResult (composed, composedDisplay); + + return new EvaluationResult (val.ToString ()); + } + + if (typeName == "System.Decimal") { + string res = CallToString (ctx, obj); + // This returns the decimal formatted using the current culture. It has to be converted to invariant culture. + decimal dec = decimal.Parse (res); + res = dec.ToString (System.Globalization.CultureInfo.InvariantCulture); + return new EvaluationResult (res); + } + + if (typeName == "System.nfloat" || typeName == "System.nint") { + return TargetObjectToObject (ctx, GetMembersSorted (ctx, null, type, obj, BindingFlags.Instance | BindingFlags.NonPublic).Single ().Value); + } + + if (IsClassInstance (ctx, obj)) { + TypeDisplayData tdata = GetTypeDisplayData (ctx, GetValueType (ctx, obj)); + if (!string.IsNullOrEmpty (tdata.ValueDisplayString) && ctx.Options.AllowDisplayStringEvaluation) { + try { + return new EvaluationResult (EvaluateDisplayString (ctx, obj, tdata.ValueDisplayString)); + } catch (MissingMemberException) { + // missing property or otherwise malformed DebuggerDisplay string + } + } + + // Return the type name + if (ctx.Options.AllowToStringCalls) { + try { + return new EvaluationResult ("{" + CallToString (ctx, obj) + "}"); + } catch (TimeOutException) { + // ToString() timed out, fall back to default behavior. + } + } + + if (!string.IsNullOrEmpty (tdata.TypeDisplayString) && ctx.Options.AllowDisplayStringEvaluation) { + try { + return new EvaluationResult ("{" + EvaluateDisplayString (ctx, obj, tdata.TypeDisplayString) + "}"); + } catch (MissingMemberException) { + // missing property or otherwise malformed DebuggerDisplay string + } + } + + return new EvaluationResult ("{" + GetDisplayTypeName (GetValueTypeName (ctx, obj)) + "}"); + } + + return new EvaluationResult ("{" + CallToString (ctx, obj) + "}"); + } + + public object Convert (EvaluationContext ctx, object obj, object targetType) + { + if (obj == null) + return null; + + object res = TryConvert (ctx, obj, targetType); + if (res != null) + return res; + + throw new EvaluatorException ("Can't convert an object of type '{0}' to type '{1}'", GetValueTypeName (ctx, obj), GetTypeName (ctx, targetType)); + } + + public virtual object TryConvert (EvaluationContext ctx, object obj, object targetType) + { + return TryCast (ctx, obj, targetType); + } + + public virtual object Cast (EvaluationContext ctx, object obj, object targetType) + { + if (obj == null) + return null; + + object res = TryCast (ctx, obj, targetType); + if (res != null) + return res; + + throw new EvaluatorException ("Can't cast an object of type '{0}' to type '{1}'", GetValueTypeName (ctx, obj), GetTypeName (ctx, targetType)); + } + + public virtual string CallToString (EvaluationContext ctx, object obj) + { + return GetValueTypeName (ctx, obj); + } + + // FIXME: next time we can break ABI/API, make this abstract + protected virtual object GetBaseTypeWithAttribute (EvaluationContext ctx, object type, object attrType) + { + return null; + } + + public object GetProxyObject (EvaluationContext ctx, object obj) + { + TypeDisplayData data = GetTypeDisplayData (ctx, GetValueType (ctx, obj)); + if (string.IsNullOrEmpty (data.ProxyType) || !ctx.Options.AllowDebuggerProxy) + return obj; + + string proxyType = data.ProxyType; + object[] typeArgs = null; + + int index = proxyType.IndexOf ('`'); + if (index != -1) { + // The proxy type is an uninstantiated generic type. + // The number of type args of the proxy must match the args of the target object + int startIndex = index + 1; + int endIndex = index + 1; + + while (endIndex < proxyType.Length && char.IsDigit (proxyType[endIndex])) + endIndex++; + + var attrType = GetType (ctx, "System.Diagnostics.DebuggerTypeProxyAttribute"); + int num = int.Parse (proxyType.Substring (startIndex, endIndex - startIndex)); + var proxiedType = GetBaseTypeWithAttribute (ctx, GetValueType (ctx, obj), attrType); + + if (proxiedType == null || !IsGenericType (ctx, proxiedType)) + return obj; + + typeArgs = GetTypeArgs (ctx, proxiedType); + if (typeArgs.Length != num) + return obj; + + if (endIndex < proxyType.Length) { + // chop off the []'d list of generic type arguments + proxyType = proxyType.Substring (0, endIndex); + } + } + + object ttype = GetType (ctx, proxyType, typeArgs); + if (ttype == null) { + // the proxy type string might be in the form: "Namespace.TypeName, Assembly...", chop off the ", Assembly..." bit. + if ((index = proxyType.IndexOf (',')) != -1) + ttype = GetType (ctx, proxyType.Substring (0, index).Trim (), typeArgs); + } + if (ttype == null) + throw new EvaluatorException ("Unknown type '{0}'", data.ProxyType); + + try { + object val = CreateValue (ctx, ttype, obj); + return val ?? obj; + } catch (EvaluatorException) { + // probably couldn't find the .ctor for the proxy type because the linker stripped it out + return obj; + } catch (Exception ex) { + ctx.WriteDebuggerError (ex); + return obj; + } + } + + public TypeDisplayData GetTypeDisplayData (EvaluationContext ctx, object type) + { + if (!IsClass (ctx, type)) + return TypeDisplayData.Default; + + TypeDisplayData td; + string tname = GetTypeName (ctx, type); + if (typeDisplayData.TryGetValue (tname, out td)) + return td; + + try { + td = OnGetTypeDisplayData (ctx, type); + } catch (Exception ex) { + ctx.WriteDebuggerError (ex); + } + + if (td == null) + typeDisplayData[tname] = td = TypeDisplayData.Default; + else + typeDisplayData[tname] = td; + + return td; + } + + protected virtual TypeDisplayData OnGetTypeDisplayData (EvaluationContext ctx, object type) + { + return null; + } + + static bool IsQuoted (string str) + { + return str.Length >= 2 && str[0] == '"' && str[str.Length - 1] == '"'; + } + + public string EvaluateDisplayString (EvaluationContext ctx, object obj, string expr) + { + var display = new StringBuilder (); + int i = expr.IndexOf ('{'); + int last = 0; + + while (i != -1 && i < expr.Length) { + display.Append (expr, last, i - last); + i++; + + int j = expr.IndexOf ('}', i); + if (j == -1) + return expr; + + string memberExpr = expr.Substring (i, j - i).Trim (); + if (memberExpr.Length == 0) + return expr; + + int comma = memberExpr.LastIndexOf (','); + bool noquotes = false; + if (comma != -1) { + var option = memberExpr.Substring (comma + 1).Trim (); + memberExpr = memberExpr.Substring (0, comma).Trim (); + noquotes |= option == "nq"; + } + + var props = memberExpr.Split (new [] { '.' }); + object val = obj; + + for (int k = 0; k < props.Length; k++) { + var member = GetMember (ctx, null, GetValueType (ctx, val), val, props [k]); + if (member != null) { + val = member.Value; + } else { + var methodName = props [k].TrimEnd ('(', ')', ' '); + if (HasMethod (ctx, GetValueType (ctx, val), methodName, BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public)) { + val = RuntimeInvoke (ctx, GetValueType (ctx, val), val, methodName, new object[0], new object[0]); + } else { + val = null; + break; + } + } + } + + if (val != null) { + var str = ctx.Evaluator.TargetObjectToString (ctx, val); + if (str == null) + display.Append ("null"); + else if (noquotes && IsQuoted (str)) + display.Append (str, 1, str.Length - 2); + else + display.Append (str); + } else { + throw new MissingMemberException (GetValueTypeName (ctx, obj), memberExpr); + } + + last = j + 1; + i = expr.IndexOf ('{', last); + } + + display.Append (expr, last, expr.Length - last); + + return display.ToString (); + } + + public void AsyncExecute (AsyncOperation operation, int timeout) + { + asyncOperationManager.Invoke (operation, timeout); + } + + public ObjectValue CreateObjectValueAsync (string name, ObjectValueFlags flags, ObjectEvaluatorDelegate evaluator) + { + return asyncEvaluationTracker.Run (name, flags, evaluator); + } + + public bool IsEvaluating { + get { return asyncEvaluationTracker.IsEvaluating; } + } + + public void CancelAsyncOperations ( ) + { + asyncEvaluationTracker.Stop (); + asyncOperationManager.AbortAll (); + asyncEvaluationTracker.WaitForStopped (); + } + + public ObjectValue GetExpressionValue (EvaluationContext ctx, string exp) + { + try { + var var = ctx.Evaluator.Evaluate (ctx, exp); + + if (var == null) + return ObjectValue.CreateUnknown (exp); + + var value = var.CreateObjectValue (ctx.Options); + value.Name = exp; + return value; + } catch (ImplicitEvaluationDisabledException) { + return ObjectValue.CreateImplicitNotSupported (ctx.ExpressionValueSource, new ObjectPath (exp), "", ObjectValueFlags.None); + } catch (NotSupportedExpressionException ex) { + return ObjectValue.CreateNotSupported (ctx.ExpressionValueSource, new ObjectPath (exp), "", ex.Message, ObjectValueFlags.None); + } catch (EvaluatorException ex) { + return ObjectValue.CreateError (ctx.ExpressionValueSource, new ObjectPath (exp), "", ex.Message, ObjectValueFlags.None); + } catch (Exception ex) { + ctx.WriteDebuggerError (ex); + return ObjectValue.CreateUnknown (exp); + } + } + + public virtual bool HasMethodWithParamLength (EvaluationContext ctx, object targetType, string methodName, BindingFlags flags, int paramLength) + { + return false; + } + + public bool HasMethod (EvaluationContext ctx, object targetType, string methodName) + { + BindingFlags flags = BindingFlags.Instance | BindingFlags.Static; + + if (!ctx.Evaluator.CaseSensitive) + flags |= BindingFlags.IgnoreCase; + + return HasMethod (ctx, targetType, methodName, null, null, flags); + } + + public bool HasMethod (EvaluationContext ctx, object targetType, string methodName, BindingFlags flags) + { + return HasMethod (ctx, targetType, methodName, null, null, flags); + } + + // argTypes can be null, meaning that it has to return true if there is any method with that name + // flags will only contain Static or Instance flags + public bool HasMethod (EvaluationContext ctx, object targetType, string methodName, object[] argTypes, BindingFlags flags) + { + return HasMethod (ctx, targetType, methodName, null, argTypes, flags); + } + + // argTypes can be null, meaning that it has to return true if there is any method with that name + // flags will only contain Static or Instance flags + public abstract bool HasMethod (EvaluationContext ctx, object targetType, string methodName, object[] genericTypeArgs, object[] argTypes, BindingFlags flags); + + // outarg `untyped lambda` + // if one of argtypes is untyped lambda, this will resolve its type. + public virtual bool HasMethod (EvaluationContext ctx, object targetType, string methodName, object[] genericTypeArgs, object[] argTypes, BindingFlags flags, out Tuple[] resolvedLambdaTypes) + { + resolvedLambdaTypes = null; + return HasMethod (ctx, targetType, methodName, genericTypeArgs, argTypes, flags); + } + + public object RuntimeInvoke (EvaluationContext ctx, object targetType, object target, string methodName, object[] argTypes, object[] argValues) + { + return RuntimeInvoke (ctx, targetType, target, methodName, null, argTypes, argValues); + } + + public virtual object RuntimeInvoke (EvaluationContext ctx, object targetType, object target, string methodName, object[] genericTypeArgs, object[] argTypes, object[] argValues, out object[] outArgs){ + outArgs = null; + return RuntimeInvoke (ctx, targetType, target, methodName, genericTypeArgs, argTypes, argValues); + } + + public abstract object RuntimeInvoke (EvaluationContext ctx, object targetType, object target, string methodName, object[] genericTypeArgs, object[] argTypes, object[] argValues); + + public virtual ValidationResult ValidateExpression (EvaluationContext ctx, string expression) + { + return ctx.Evaluator.ValidateExpression (ctx, expression); + } + } + + public class TypeDisplayData + { + public string ProxyType { get; internal set; } + public string ValueDisplayString { get; internal set; } + public string TypeDisplayString { get; internal set; } + public string NameDisplayString { get; internal set; } + public bool IsCompilerGenerated { get; internal set; } + + public bool IsProxyType { + get { return ProxyType != null; } + } + + public static readonly TypeDisplayData Default = new TypeDisplayData (null, null, null, null, false, null); + + public Dictionary MemberData { get; internal set; } + + public TypeDisplayData (string proxyType, string valueDisplayString, string typeDisplayString, + string nameDisplayString, bool isCompilerGenerated, Dictionary memberData) + { + ProxyType = proxyType; + ValueDisplayString = valueDisplayString; + TypeDisplayString = typeDisplayString; + NameDisplayString = nameDisplayString; + IsCompilerGenerated = isCompilerGenerated; + MemberData = memberData; + } + + public DebuggerBrowsableState GetMemberBrowsableState (string name) + { + if (MemberData == null) + return DebuggerBrowsableState.Collapsed; + + DebuggerBrowsableState state; + if (!MemberData.TryGetValue (name, out state)) + state = DebuggerBrowsableState.Collapsed; + + return state; + } + } + + class ObjectValueNameTracker + { + readonly Dictionary> names = new Dictionary> (); + readonly EvaluationContext ctx; + + public ObjectValueNameTracker (EvaluationContext ctx) + { + this.ctx = ctx; + } + + /// + /// Disambiguate the ObjectValue's name (in the case where the property name also exists in a base class). + /// + /// + /// The ValueReference. + /// + /// + /// The ObjectValue. + /// + public void Disambiguate (ValueReference val, ObjectValue oval) + { + KeyValuePair other; + + if (names.TryGetValue (oval.Name, out other)) { + object tn = val.DeclaringType; + + if (tn != null) + oval.Name += " (" + ctx.Adapter.GetDisplayTypeName (ctx, tn) + ")"; + if (!other.Key.Name.EndsWith (")", StringComparison.Ordinal)) { + tn = other.Value.DeclaringType; + if (tn != null) + other.Key.Name += " (" + ctx.Adapter.GetDisplayTypeName (ctx, tn) + ")"; + } + } + + names [oval.Name] = new KeyValuePair (oval, val); + } + } + + public struct EnumMember + { + public string Name { get; set; } + public long Value { get; set; } + } +} diff --git a/VisualPascalABCNETLinux/MonoDebugging/Evaluation/RawViewSource.cs b/VisualPascalABCNETLinux/MonoDebugging/Evaluation/RawViewSource.cs new file mode 100644 index 000000000..5c063c94b --- /dev/null +++ b/VisualPascalABCNETLinux/MonoDebugging/Evaluation/RawViewSource.cs @@ -0,0 +1,83 @@ +// RawViewSource.cs +// +// Authors: Lluis Sanchez Gual +// Jeffrey Stedfast +// +// Copyright (c) 2008 Novell, Inc (http://www.novell.com) +// Copyright (c) 2012 Xamarin Inc. (http://www.xamarin.com) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// +// + +using System; +using Mono.Debugging.Backend; +using Mono.Debugging.Client; + +namespace Mono.Debugging.Evaluation +{ + public class RawViewSource: RemoteFrameObject, IObjectValueSource + { + object obj; + EvaluationContext ctx; + IObjectSource objectSource; + + public RawViewSource (EvaluationContext ctx, IObjectSource objectSource, object obj) + { + this.ctx = ctx; + this.obj = obj; + this.objectSource = objectSource; + } + + public static ObjectValue CreateRawView (EvaluationContext ctx, IObjectSource objectSource, object obj) + { + RawViewSource src = new RawViewSource (ctx, objectSource, obj); + src.Connect (); + ObjectValue val = ObjectValue.CreateObject (src, new ObjectPath ("Raw View"), "", "", ObjectValueFlags.Group|ObjectValueFlags.ReadOnly|ObjectValueFlags.NoRefresh, null); + val.ChildSelector = ""; + return val; + } + + public ObjectValue[] GetChildren (ObjectPath path, int index, int count, EvaluationOptions options) + { + EvaluationContext cctx = ctx.WithOptions (options); + return cctx.Adapter.GetObjectValueChildren (cctx, objectSource, cctx.Adapter.GetValueType (cctx, obj), obj, index, count, false); + } + + public ObjectValue GetValue (ObjectPath path, EvaluationOptions options) + { + throw new NotSupportedException (); + } + + public EvaluationResult SetValue (ObjectPath path, string value, EvaluationOptions options) + { + throw new NotSupportedException (); + } + + public void SetRawValue (ObjectPath path, object value, EvaluationOptions options) + { + throw new NotImplementedException (); + } + + public object GetRawValue (ObjectPath path, EvaluationOptions options) + { + throw new NotImplementedException (); + } + } +} diff --git a/VisualPascalABCNETLinux/MonoDebugging/Evaluation/RemoteFrameObject.cs b/VisualPascalABCNETLinux/MonoDebugging/Evaluation/RemoteFrameObject.cs new file mode 100644 index 000000000..7afb72592 --- /dev/null +++ b/VisualPascalABCNETLinux/MonoDebugging/Evaluation/RemoteFrameObject.cs @@ -0,0 +1,78 @@ +// RemoteFrameObject.cs +// +// Author: +// Lluis Sanchez Gual +// +// Copyright (c) 2008 Novell, Inc (http://www.novell.com) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// +// + +using System; +using System.Collections.Generic; + +namespace Mono.Debugging.Evaluation +{ + public class RemoteFrameObject: MarshalByRefObject + { + static List connectedValues = new List (); + + public static bool TrackConnections { get; set; } + + bool connected; + + public void Connect () + { + if (!TrackConnections) + return; + + // Registers the value reference. Once a remote reference of this object + // is created, it will never be released, until DisconnectAll is called, + // which is done every time the current backtrace changes + + lock (connectedValues) { + if (!connected) { + connectedValues.Add (this); + connected = true; + } + } + } + + public static void DisconnectAll () + { + lock (connectedValues) { + foreach (RemoteFrameObject val in connectedValues) { +#if !NETCOREAPP + System.Runtime.Remoting.RemotingServices.Disconnect (val); +#endif + IDisposable disp = val as IDisposable; + if (disp != null) + disp.Dispose (); + } + connectedValues.Clear (); + } + } + + public override object InitializeLifetimeService () + { + return null; + } + } +} diff --git a/VisualPascalABCNETLinux/MonoDebugging/Evaluation/RemoteRawValue.cs b/VisualPascalABCNETLinux/MonoDebugging/Evaluation/RemoteRawValue.cs new file mode 100644 index 000000000..fdd94dd51 --- /dev/null +++ b/VisualPascalABCNETLinux/MonoDebugging/Evaluation/RemoteRawValue.cs @@ -0,0 +1,241 @@ +// +// RemoteRawValue.cs +// +// Author: +// Lluis Sanchez Gual +// +// Copyright (c) 2010 Novell, Inc (http://www.novell.com) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +using System; +using Mono.Debugging.Backend; +using System.Collections; +using Mono.Debugging.Client; + +namespace Mono.Debugging.Evaluation +{ + class RemoteRawValue: RemoteFrameObject, IRawValue + { + readonly EvaluationContext ctx; + readonly IObjectSource source; + readonly object targetObject; + + public RemoteRawValue (EvaluationContext gctx, IObjectSource source, object targetObject) + { + this.ctx = gctx.Clone (); + ctx.Options.AllowTargetInvoke = true; + ctx.Options.AllowMethodEvaluation = true; + this.targetObject = targetObject; + this.source = source; + Connect (); + } + + public object TargetObject { + get { return targetObject; } + } + + #region IRawValue implementation + public object CallMethod (string name, object[] parameters, EvaluationOptions options) + { + object[] outArgs; + return CallMethod (name, parameters, false, out outArgs, options); + } + + public object CallMethod (string name, object[] parameters, out object[] outArgs, EvaluationOptions options) + { + return CallMethod (name, parameters, true, out outArgs, options); + } + + private object CallMethod (string name, object[] parameters, bool enableOutArgs, out object[] outArgs, EvaluationOptions options) + { + var localContext = ctx.WithOptions (options); + + var argValues = new object [parameters.Length]; + var argTypes = new object [parameters.Length]; + + for (int n = 0; n < argValues.Length; n++) { + argValues[n] = localContext.Adapter.FromRawValue (localContext, parameters[n]); + argTypes[n] = localContext.Adapter.GetValueType (localContext, argValues[n]); + } + + var type = localContext.Adapter.GetValueType (localContext, targetObject); + if (enableOutArgs) { + object[] outArgsTemp; + var res = localContext.Adapter.RuntimeInvoke (localContext, type, targetObject, name, null, argTypes, argValues, out outArgsTemp); + outArgs = new object[outArgsTemp.Length]; + for (int i = 0; i < outArgs.Length; i++) { + outArgs [i] = localContext.Adapter.ToRawValue (localContext, null, outArgsTemp [i]); + } + return localContext.Adapter.ToRawValue (localContext, null, res); + } else { + outArgs = null; + var res = localContext.Adapter.RuntimeInvoke (localContext, type, targetObject, name, argTypes, argValues); + return localContext.Adapter.ToRawValue (localContext, null, res); + } + } + + public object GetMemberValue (string name, EvaluationOptions options) + { + var localContext = ctx.WithOptions (options); + var type = localContext.Adapter.GetValueType (localContext, targetObject); + var val = localContext.Adapter.GetMember (localContext, source, type, targetObject, name); + + if (val == null) + throw new EvaluatorException ("Member '{0}' not found", name); + + return localContext.Adapter.ToRawValue (localContext, val, val.Value); + } + + public void SetMemberValue (string name, object value, EvaluationOptions options) + { + var localContext = ctx.WithOptions (options); + var type = localContext.Adapter.GetValueType (localContext, targetObject); + var val = localContext.Adapter.GetMember (localContext, source, type, targetObject, name); + + if (val == null) + throw new EvaluatorException ("Member '{0}' not found", name); + + val.Value = localContext.Adapter.FromRawValue (localContext, value); + } + + #endregion + } + + class RemoteRawValueArray: RemoteFrameObject, IRawValueArray + { + readonly ICollectionAdaptor targetArray; + readonly EvaluationContext ctx; + readonly IObjectSource source; + readonly object targetObject; + + public RemoteRawValueArray (EvaluationContext ctx, IObjectSource source, ICollectionAdaptor targetArray, object targetObject) + { + this.ctx = ctx; + this.targetArray = targetArray; + this.targetObject = targetObject; + this.source = source; + Connect (); + } + + public object TargetObject { + get { return targetObject; } + } + + public object GetValue (int[] index) + { + return ctx.Adapter.ToRawValue (ctx, source, targetArray.GetElement (index)); + } + + public Array GetValues (int[] index, int count) + { + var values = targetArray.GetElements (index, count); + var idx = new int[index.Length]; + var array = new ArrayList (); + + for (int i = 0; i < index.Length; i++) + idx[i] = index[i]; + + Type commonType = null; + for (int i = 0; i < count; i++) { + var rv = ctx.Adapter.ToRawValue (ctx, new ArrayObjectSource (targetArray, idx), values.GetValue (i)); + if (commonType == null) + commonType = rv.GetType (); + else if (commonType != rv.GetType ()) + commonType = typeof (void); + array.Add (rv); + + idx[idx.Length - 1]++; + } + + if (array.Count > 0 && commonType != typeof (void)) + return array.ToArray (commonType); + + return array.ToArray (); + } + + public void SetValue (int[] index, object value) + { + targetArray.SetElement (index, ctx.Adapter.FromRawValue (ctx, value)); + } + + public int[] Dimensions { + get { + return targetArray.GetDimensions (); + } + } + + public Array ToArray () + { + int[] dims = targetArray.GetDimensions (); + var array = new ArrayList (); + + if (dims.Length != 1) + throw new NotSupportedException (); + + var idx = new int [1]; + Type commonType = null; + for (int n = 0; n < dims[0]; n++) { + idx[0] = n; + + var rv = ctx.Adapter.ToRawValue (ctx, new ArrayObjectSource (targetArray, idx), targetArray.GetElement (idx)); + if (commonType == null) + commonType = rv.GetType (); + else if (commonType != rv.GetType ()) + commonType = typeof(void); + array.Add (rv); + } + + if (array.Count > 0 && commonType != typeof(void)) + return array.ToArray (commonType); + + return array.ToArray (); + } + } + + class RemoteRawValueString: RemoteFrameObject, IRawValueString + { + readonly IStringAdaptor targetString; + readonly object targetObject; + + public RemoteRawValueString (IStringAdaptor targetString, object targetObject) + { + this.targetString = targetString; + this.targetObject = targetObject; + Connect (); + } + + public object TargetObject { + get { return targetObject; } + } + + public int Length { + get { return targetString.Length; } + } + + public string Value { + get { return targetString.Value; } + } + + public string Substring (int index, int length) + { + return targetString.Substring (index, length); + } + } +} diff --git a/VisualPascalABCNETLinux/MonoDebugging/Evaluation/TimeOutException.cs b/VisualPascalABCNETLinux/MonoDebugging/Evaluation/TimeOutException.cs new file mode 100644 index 000000000..d16938162 --- /dev/null +++ b/VisualPascalABCNETLinux/MonoDebugging/Evaluation/TimeOutException.cs @@ -0,0 +1,38 @@ +// TimeOutException.cs +// +// Author: +// Lluis Sanchez Gual +// +// Copyright (c) 2008 Novell, Inc (http://www.novell.com) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// +// + +using System; + +namespace Mono.Debugging.Evaluation +{ + public class TimeOutException: EvaluatorException + { + public TimeOutException (): base ("Timed out.") + { + } + } +} diff --git a/VisualPascalABCNETLinux/MonoDebugging/Evaluation/TimedEvaluator.cs b/VisualPascalABCNETLinux/MonoDebugging/Evaluation/TimedEvaluator.cs new file mode 100644 index 000000000..36bc19dc1 --- /dev/null +++ b/VisualPascalABCNETLinux/MonoDebugging/Evaluation/TimedEvaluator.cs @@ -0,0 +1,219 @@ +// EvaluationContext.cs +// +// Author: +// Lluis Sanchez Gual +// +// Copyright (c) 2008 Novell, Inc (http://www.novell.com) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// +// + +using System; +using System.Collections.Generic; +using System.Threading; + +namespace Mono.Debugging.Evaluation +{ + public class TimedEvaluator + { + const int maxThreads = 1; + + object runningLock = new object (); + Queue pendingTasks = new Queue (); + ManualResetEvent newTaskEvent = new ManualResetEvent (false); + AutoResetEvent threadNotBusyEvent = new AutoResetEvent (false); + ManualResetEvent disposedEvent = new ManualResetEvent (false); + List executingTasks = new List (); + int runningThreads; + int busyThreads; + bool useTimeout; + bool disposed; + static int threadNameId; + + public TimedEvaluator () : this (true) + { + } + + public TimedEvaluator (bool useTimeout) + { + RunTimeout = 1000; + this.useTimeout = useTimeout; + } + + public int RunTimeout { get; set; } + + public bool IsEvaluating { + get { + lock (runningLock) { + return pendingTasks.Count > 0 || busyThreads > 0; + } + } + } + + /// + /// Executes the provided evaluator. If a result is obtained before RunTimeout milliseconds, + /// the method ends returning True. + /// If it does not finish after RunTimeout milliseconds, the method ends retuning False, although + /// the evaluation continues in the background. In that case, when evaluation ends, the provided + /// delayedDoneCallback delegate is called. + /// + public bool Run (EvaluatorDelegate evaluator, EvaluatorDelegate delayedDoneCallback) + { + if (!useTimeout) { + SafeRun (evaluator); + return true; + } + + Task task = new Task (); + task.Evaluator = evaluator; + task.FinishedCallback = delayedDoneCallback; + + lock (runningLock) { + if (disposed) + return false; + if (busyThreads == runningThreads && runningThreads < maxThreads) { + runningThreads++; + var tr = new Thread (Runner); + tr.Name = "Debugger evaluator " + threadNameId++; + tr.IsBackground = true; + tr.Start (); + } + pendingTasks.Enqueue (task); + if (busyThreads == runningThreads) { + task.TimedOut = true; + return false; + } + newTaskEvent.Set (); + } + WaitHandle.WaitAny (new WaitHandle [] { task.RunningEvent, disposedEvent }); + if (WaitHandle.WaitAny (new WaitHandle [] { task.RunFinishedEvent, disposedEvent }, TimeSpan.FromMilliseconds (RunTimeout), false) != 0) { + lock (task) { + if (task.Processed) { + return true; + } else { + task.TimedOut = true; + return false; + } + } + } + return true; + } + + void Runner () + { + Task threadTask = null; + + while (!disposed) { + + if (threadTask == null) { + lock (runningLock) { + if (disposed) { + runningThreads--; + return; + } + if (pendingTasks.Count > 0) { + threadTask = pendingTasks.Dequeue (); + executingTasks.Add (threadTask); + busyThreads++; + } else if (busyThreads + 1 < runningThreads) { + runningThreads--;//If we got extra non-busy threads, close this one... + return; + } + if (threadTask == null) { + newTaskEvent.Reset (); + } + } + //No pending task, wait for it + if (threadTask == null) { + WaitHandle.WaitAny (new WaitHandle [] { newTaskEvent, disposedEvent }); + continue; + } + } else { + lock (runningLock) { + executingTasks.Remove (threadTask); + busyThreads--; + } + threadNotBusyEvent.Set (); + threadTask = null; + continue; + } + + threadTask.RunningEvent.Set (); + SafeRun (threadTask.Evaluator); + threadTask.RunFinishedEvent.Set (); + lock (threadTask) { + threadTask.Processed = true; + if (threadTask.TimedOut && !disposed) { + SafeRun (threadTask.FinishedCallback); + } + } + } + } + + public void Dispose () + { + lock (runningLock) { + disposed = true; + CancelAll (); + disposedEvent.Set (); + } + } + + public void CancelAll () + { + lock (runningLock) { + pendingTasks.Clear (); + // If there is a task waiting the be picked by the runner, + // set the task wait events to avoid deadlocking the caller. + executingTasks.ForEach (t => { + t.RunningEvent.Set (); + t.RunFinishedEvent.Set (); + }); + } + } + + public void WaitForStopped () + { + while (busyThreads > 0) { + threadNotBusyEvent.WaitOne (1000); + } + } + + void SafeRun (EvaluatorDelegate del) + { + try { + del (); + } catch { + } + } + + class Task + { + public ManualResetEvent RunningEvent = new ManualResetEvent (false); + public ManualResetEvent RunFinishedEvent = new ManualResetEvent (false); + public EvaluatorDelegate Evaluator; + public EvaluatorDelegate FinishedCallback; + public bool TimedOut; + public bool Processed; + } + } + + public delegate void EvaluatorDelegate (); +} diff --git a/VisualPascalABCNETLinux/MonoDebugging/Evaluation/TypeValueReference.cs b/VisualPascalABCNETLinux/MonoDebugging/Evaluation/TypeValueReference.cs new file mode 100644 index 000000000..9a2ee0ad2 --- /dev/null +++ b/VisualPascalABCNETLinux/MonoDebugging/Evaluation/TypeValueReference.cs @@ -0,0 +1,207 @@ +// TypeValueReference.cs +// +// Author: +// Lluis Sanchez Gual +// +// Copyright (c) 2008 Novell, Inc (http://www.novell.com) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// +// + +using System; +using System.Reflection; +using System.Diagnostics; +using System.Collections.Generic; + +using Mono.Debugging.Client; + +namespace Mono.Debugging.Evaluation +{ + public class TypeValueReference: ValueReference + { + readonly string fullName; + readonly string name; + readonly object type; + + public TypeValueReference (EvaluationContext ctx, object type) : base (ctx) + { + this.type = type; + fullName = ctx.Adapter.GetDisplayTypeName (ctx, type); + name = GetTypeName (fullName); + } + + internal static string GetTypeName (string tname) + { + tname = tname.Replace ('+', '.'); + + int sep1 = tname.IndexOf ('<'); + int sep2 = tname.IndexOf ('['); + + if (sep2 != -1 && (sep2 < sep1 || sep1 == -1)) + sep1 = sep2; + + if (sep1 == -1) + sep1 = tname.Length - 1; + + int dot = tname.LastIndexOf ('.', sep1); + + return dot != -1 ? tname.Substring (dot + 1) : tname; + } + + public override object Value { + get { + throw new NotSupportedException (); + } + set { + throw new NotSupportedException(); + } + } + + public override object Type { + get { + return type; + } + } + + public override object ObjectValue { + get { + throw new NotSupportedException (); + } + } + + public override string Name { + get { + return name; + } + } + + public override ObjectValueFlags Flags { + get { + return ObjectValueFlags.Type; + } + } + + protected override ObjectValue OnCreateObjectValue (EvaluationOptions options) + { + return Mono.Debugging.Client.ObjectValue.CreateObject (this, new ObjectPath (Name), "", fullName, Flags, null); + } + + public override ValueReference GetChild (string name, EvaluationOptions options) + { + var ctx = GetContext (options); + + foreach (var val in ctx.Adapter.GetMembers (ctx, this, type, null)) { + if (val.Name == name) + return val; + } + + foreach (var nestedType in ctx.Adapter.GetNestedTypes (ctx, type)) { + string typeName = ctx.Adapter.GetTypeName (ctx, nestedType); + + if (GetTypeName (typeName) == name) + return new TypeValueReference (ctx, nestedType); + } + + return null; + } + + public override ObjectValue[] GetChildren (ObjectPath path, int index, int count, EvaluationOptions options) + { + var ctx = GetContext (options); + + try { + BindingFlags flattenFlag = options.FlattenHierarchy ? (BindingFlags)0 : BindingFlags.DeclaredOnly; + BindingFlags flags = BindingFlags.Static | BindingFlags.Public | flattenFlag; + bool groupPrivateMembers = options.GroupPrivateMembers || ctx.Adapter.IsExternalType (ctx, type); + var list = new List (); + + if (!groupPrivateMembers) + flags |= BindingFlags.NonPublic; + + var tdata = ctx.Adapter.GetTypeDisplayData (ctx, type); + var tdataType = type; + + foreach (var val in ctx.Adapter.GetMembersSorted (ctx, this, type, null, flags)) { + var decType = val.DeclaringType; + if (decType != null && decType != tdataType) { + tdataType = decType; + tdata = ctx.Adapter.GetTypeDisplayData (ctx, decType); + } + + var state = tdata.GetMemberBrowsableState (val.Name); + if (state == DebuggerBrowsableState.Never) + continue; + + var oval = val.CreateObjectValue (options); + list.Add (oval); + } + + var nestedTypes = new List (); + foreach (var nestedType in ctx.Adapter.GetNestedTypes (ctx, type)) + nestedTypes.Add (new TypeValueReference (ctx, nestedType).CreateObjectValue (options)); + + nestedTypes.Sort ((v1, v2) => string.Compare (v1.Name, v2.Name, StringComparison.CurrentCulture)); + + list.AddRange (nestedTypes); + + if (groupPrivateMembers) + list.Add (FilteredMembersSource.CreateNonPublicsNode (ctx, this, type, null, BindingFlags.NonPublic | BindingFlags.Static | flattenFlag)); + + if (!options.FlattenHierarchy) { + object baseType = ctx.Adapter.GetBaseType (ctx, type, false); + if (baseType != null) { + var baseRef = new TypeValueReference (ctx, baseType); + var baseVal = baseRef.CreateObjectValue (false); + baseVal.Name = "base"; + list.Insert (0, baseVal); + } + } + + return list.ToArray (); + } catch (Exception ex) { + ctx.WriteDebuggerOutput (ex.Message); + return new ObjectValue [0]; + } + } + + public override IEnumerable GetChildReferences (EvaluationOptions options) + { + var ctx = GetContext (options); + + try { + var list = new List (); + + list.AddRange (ctx.Adapter.GetMembersSorted (ctx, this, type, null, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static)); + + var nestedTypes = new List (); + foreach (var nestedType in ctx.Adapter.GetNestedTypes (ctx, type)) + nestedTypes.Add (new TypeValueReference (ctx, nestedType)); + + nestedTypes.Sort ((v1, v2) => string.Compare (v1.Name, v2.Name, StringComparison.CurrentCulture)); + list.AddRange (nestedTypes); + + return list; + } catch (Exception ex) { + ctx.WriteDebuggerOutput (ex.Message); + return new ValueReference[0]; + } + } + } +} diff --git a/VisualPascalABCNETLinux/MonoDebugging/Evaluation/UserVariableReference.cs b/VisualPascalABCNETLinux/MonoDebugging/Evaluation/UserVariableReference.cs new file mode 100644 index 000000000..d5a5d90aa --- /dev/null +++ b/VisualPascalABCNETLinux/MonoDebugging/Evaluation/UserVariableReference.cs @@ -0,0 +1,71 @@ +// +// UserVariableReference.cs +// +// Author: +// Lluis Sanchez Gual +// +// Copyright (c) 2009 Novell, Inc (http://www.novell.com) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +using Mono.Debugging.Client; + +namespace Mono.Debugging.Evaluation +{ + public class UserVariableReference: ValueReference + { + readonly string name; + object currentValue; + + public UserVariableReference (EvaluationContext ctx, string name): base (ctx) + { + this.name = name; + } + + public override string Name { + get { + return name; + } + } + + public override object Value { + get { + if (currentValue != null) + return currentValue; + + throw new EvaluatorException ("Value undefined."); + } + set { + currentValue = value; + } + } + + public override object Type { + get { + return Context.Adapter.GetValueType (Context, Value); + } + } + + public override ObjectValueFlags Flags { + get { + return ObjectValueFlags.Variable; + } + } + } +} diff --git a/VisualPascalABCNETLinux/MonoDebugging/Evaluation/ValueReference.cs b/VisualPascalABCNETLinux/MonoDebugging/Evaluation/ValueReference.cs new file mode 100644 index 000000000..58470a4df --- /dev/null +++ b/VisualPascalABCNETLinux/MonoDebugging/Evaluation/ValueReference.cs @@ -0,0 +1,307 @@ +// ValueReference.cs +// +// Author: +// Lluis Sanchez Gual +// +// Copyright (c) 2008 Novell, Inc (http://www.novell.com) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// +// + +using System; +using System.Collections.Generic; +using Mono.Debugging.Client; +using Mono.Debugging.Backend; +using DC = Mono.Debugging.Client; + +namespace Mono.Debugging.Evaluation +{ + public abstract class ValueReference: RemoteFrameObject, IObjectValueSource, IObjectSource + { + readonly EvaluationOptions originalOptions; + + protected ValueReference (EvaluationContext ctx) + { + originalOptions = ctx.Options; + Context = ctx; + } + + public virtual object ObjectValue { + get { + object ob = Value; + if (Context.Adapter.IsNull (Context, ob)) + return null; + + if (Context.Adapter.IsPrimitive (Context, ob)) + return Context.Adapter.TargetObjectToObject (Context, ob); + + return ob; + } + } + + public abstract object Value { get; set; } + public abstract string Name { get; } + public abstract object Type { get; } + public abstract ObjectValueFlags Flags { get; } + + // For class members, the type declaring the member (null otherwise) + public virtual object DeclaringType { + get { return null; } + } + + public EvaluationContext Context { + get; set; + } + + public EvaluationContext GetContext (EvaluationOptions options) + { + return Context.WithOptions (options); + } + + public ObjectValue CreateObjectValue (bool withTimeout) + { + return CreateObjectValue (withTimeout, Context.Options); + } + + public ObjectValue CreateObjectValue (bool withTimeout, EvaluationOptions options) + { + if (!CanEvaluate (options)) + return DC.ObjectValue.CreateImplicitNotSupported (this, new ObjectPath (Name), Context.Adapter.GetDisplayTypeName (GetContext (options), Type), Flags); + + if (withTimeout) { + return Context.Adapter.CreateObjectValueAsync (Name, Flags, delegate { + return CreateObjectValue (options); + }); + } + + return CreateObjectValue (options); + } + + public ObjectValue CreateObjectValue (EvaluationOptions options) + { + if (!CanEvaluate (options)) { + if (options.AllowTargetInvoke)//If it can't evaluate and target invoke is allowed, mark it as not supported. + return DC.ObjectValue.CreateNotSupported (this, new ObjectPath (Name), Context.Adapter.GetDisplayTypeName (GetContext (options), Type), "Can not evaluate", Flags); + return DC.ObjectValue.CreateImplicitNotSupported (this, new ObjectPath (Name), Context.Adapter.GetDisplayTypeName (GetContext (options), Type), Flags); + } + Connect (); + try { + return OnCreateObjectValue (options); + } catch (ImplicitEvaluationDisabledException) { + return DC.ObjectValue.CreateImplicitNotSupported (this, new ObjectPath (Name), Context.Adapter.GetDisplayTypeName (GetContext (options), Type), Flags); + } catch (NotSupportedExpressionException ex) { + return DC.ObjectValue.CreateNotSupported (this, new ObjectPath (Name), Context.Adapter.GetDisplayTypeName (GetContext (options), Type), ex.Message, Flags); + } catch (EvaluatorException ex) { + return DC.ObjectValue.CreateError (this, new ObjectPath (Name), Context.Adapter.GetDisplayTypeName (GetContext (options), Type), ex.Message, Flags); + } catch (Exception ex) { + Context.WriteDebuggerError (ex); + return DC.ObjectValue.CreateUnknown (Name); + } + } + + protected virtual bool CanEvaluate (EvaluationOptions options) + { + return true; + } + + protected virtual ObjectValue OnCreateObjectValue (EvaluationOptions options) + { + string name = Name; + if (string.IsNullOrEmpty (name)) + name = "?"; + + var ctx = GetContext (options); + object val = null; + + // Note: The Value property implementation may make use of the EvaluationOptions, + // so we need to override our context temporarily to do the evaluation. + val = GetValue (ctx); + + if (val != null && !ctx.Adapter.IsNull (ctx, val)) + return ctx.Adapter.CreateObjectValue (ctx, this, new ObjectPath (name), val, Flags); + + return DC.ObjectValue.CreateNullObject (this, name, ctx.Adapter.GetDisplayTypeName (ctx.Adapter.GetTypeName (ctx, Type)), Flags); + } + + ObjectValue IObjectValueSource.GetValue (ObjectPath path, EvaluationOptions options) + { + return CreateObjectValue (true, options); + } + + EvaluationResult IObjectValueSource.SetValue (ObjectPath path, string value, EvaluationOptions options) + { + try { + Context.WaitRuntimeInvokes (); + + var ctx = GetContext (options); + ctx.Options.AllowMethodEvaluation = true; + ctx.Options.AllowTargetInvoke = true; + + var vref = ctx.Evaluator.Evaluate (ctx, value, Type); + var newValue = ctx.Adapter.Convert (ctx, vref.Value, Type); + SetValue (ctx, newValue); + } catch (Exception ex) { + Context.WriteDebuggerError (ex); + Context.WriteDebuggerOutput ("Value assignment failed: {0}: {1}\n", ex.GetType (), ex.Message); + } + + try { + return Context.Evaluator.TargetObjectToExpression (Context, Value); + } catch (Exception ex) { + Context.WriteDebuggerError (ex); + Context.WriteDebuggerOutput ("Value assignment failed: {0}: {1}\n", ex.GetType (), ex.Message); + } + + return null; + } + + object IObjectValueSource.GetRawValue (ObjectPath path, EvaluationOptions options) + { + var ctx = GetContext (options); + + return ctx.Adapter.ToRawValue (ctx, this, GetValue (ctx)); + } + + void IObjectValueSource.SetRawValue (ObjectPath path, object value, EvaluationOptions options) + { + SetRawValue (path, value, options); + } + + public object GetRawValue (EvaluationOptions options) + { + var ctx = GetContext (options); + return ctx.Adapter.ToRawValue (ctx, this, GetValue (ctx)); + } + + public void SetRawValue (object value, EvaluationOptions options) + { + SetRawValue (new ObjectPath (), value, options); + } + + protected virtual void SetRawValue (ObjectPath path, object value, EvaluationOptions options) + { + var ctx = GetContext (options); + + SetValue (ctx, Context.Adapter.FromRawValue (ctx, value)); + } + + ObjectValue[] IObjectValueSource.GetChildren (ObjectPath path, int index, int count, EvaluationOptions options) + { + return GetChildren (path, index, count, options); + } + + public virtual string CallToString () + { + return Context.Adapter.CallToString (Context, Value); + } + + public virtual object GetValue (EvaluationContext ctx) + { + return Value; + } + + public virtual void SetValue (EvaluationContext ctx, object value) + { + Value = value; + } + + [Obsolete ("Use GetValue(EvaluationContext) instead.")] + protected virtual object GetValueExplicitly () + { + var options = Context.Options.Clone (); + options.AllowTargetInvoke = true; + var ctx = GetContext (options); + + return GetValue (ctx); + } + + public virtual ObjectValue[] GetChildren (ObjectPath path, int index, int count, EvaluationOptions options) + { + try { + var ctx = GetChildrenContext (options); + + return ctx.Adapter.GetObjectValueChildren (ctx, this, GetValue (ctx), index, count); + } catch (Exception ex) { + return new [] { DC.ObjectValue.CreateFatalError ("", ex.Message, ObjectValueFlags.ReadOnly) }; + } + } + + public virtual IEnumerable GetChildReferences (EvaluationOptions options) + { + try { + object val = Value; + if (Context.Adapter.IsClassInstance (Context, val)) + return Context.Adapter.GetMembersSorted (GetChildrenContext (options), this, Type, val); + } catch { + // Ignore + } + + return new ValueReference [0]; + } + + public IObjectSource ParentSource { get; set; } + + protected EvaluationContext GetChildrenContext (EvaluationOptions options) + { + var ctx = Context.Clone (); + + if (options != null) + ctx.Options = options; + + ctx.Options.EvaluationTimeout = originalOptions.MemberEvaluationTimeout; + + return ctx; + } + + public virtual ValueReference GetChild (ObjectPath vpath, EvaluationOptions options) + { + if (vpath.Length == 0) + return this; + + var val = GetChild (vpath[0], options); + + return val != null ? val.GetChild (vpath.GetSubpath (1), options) : null; + } + + public virtual ValueReference GetChild (string name, EvaluationOptions options) + { + object obj = Value; + + if (obj == null) + return null; + + if (name[0] == '[' && Context.Adapter.IsArray (Context, obj)) { + // Parse the array indices + var tokens = name.Substring (1, name.Length - 2).Split (','); + var indices = new int [tokens.Length]; + + for (int n = 0; n < tokens.Length; n++) + indices[n] = int.Parse (tokens[n]); + + return new ArrayValueReference (Context, obj, indices); + } + + if (Context.Adapter.IsClassInstance (Context, obj)) + return Context.Adapter.GetMember (GetChildrenContext (options), this, Type, obj, name); + + return null; + } + } +} diff --git a/VisualPascalABCNETLinux/MonoDebugging/Soft/ArrayAdaptor.cs b/VisualPascalABCNETLinux/MonoDebugging/Soft/ArrayAdaptor.cs new file mode 100644 index 000000000..0225f0ea4 --- /dev/null +++ b/VisualPascalABCNETLinux/MonoDebugging/Soft/ArrayAdaptor.cs @@ -0,0 +1,102 @@ +// +// ArrayAdaptor.cs +// +// Author: +// Lluis Sanchez Gual +// +// Copyright (c) 2009 Novell, Inc (http://www.novell.com) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +using System; +using System.Linq; +using Mono.Debugging.Evaluation; +using Mono.Debugger.Soft; + +namespace Mono.Debugging.Soft +{ + public class ArrayAdaptor : ICollectionAdaptor + { + readonly ArrayMirror array; + int[] dimensions, bounds; + + public ArrayAdaptor (ArrayMirror array) + { + this.array = array; + } + + public int[] GetLowerBounds () + { + if (bounds == null) { + bounds = new int[array.Rank]; + for (int i = 0; i < array.Rank; i++) + bounds[i] = array.GetLowerBound (i); + } + + return bounds; + } + + public int[] GetDimensions () + { + if (dimensions == null) { + dimensions = new int[array.Rank]; + for (int i = 0; i < array.Rank; i++) + dimensions[i] = array.GetLength (i); + } + + return dimensions; + } + + public object GetElement (int[] indices) + { + int i = GetIndex (indices); + return array.GetValues (i, 1)[0]; + } + + public Array GetElements (int[] indices, int count) + { + int i = GetIndex (indices); + return array.GetValues (i, count).ToArray (); + } + + public void SetElement (int[] indices, object val) + { + array.SetValues (GetIndex (indices), new Value[] { (Value) val }); + } + + int GetIndex (int [] indices) + { + int ts = 1; + int i = 0; + int [] dims = GetDimensions (); + var lowerBounds = GetLowerBounds (); + for (int n = indices.Length - 1; n >= 0; n--) { + i += (indices [n] - lowerBounds [n]) * ts; + ts *= dims [n]; + } + return i; + } + + public object ElementType { + get { + return array.Type.GetElementType (); + } + } + } +} diff --git a/VisualPascalABCNETLinux/MonoDebugging/Soft/FieldReferenceBatch.cs b/VisualPascalABCNETLinux/MonoDebugging/Soft/FieldReferenceBatch.cs new file mode 100644 index 000000000..ab6db44ae --- /dev/null +++ b/VisualPascalABCNETLinux/MonoDebugging/Soft/FieldReferenceBatch.cs @@ -0,0 +1,66 @@ +// +// FieldReferenceBatch.cs +// +// Authors: David Karlaš +// +// Copyright (c) 2014 Xamarin Inc. (http://www.xamarin.com) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +using Mono.Debugger.Soft; +using System.Collections.Generic; + +namespace Mono.Debugging.Soft +{ + public class FieldReferenceBatch + { + readonly object locker = new object (); + readonly object obj; + List fields = new List (); + object[] results; + + public FieldReferenceBatch (object obj) + { + this.obj = obj; + } + + public void Add (FieldInfoMirror fieldVal) + { + lock (locker) { + fields.Add (fieldVal); + } + } + + public object GetValue (FieldInfoMirror field) + { + lock (locker) { + if (results == null || fields.Count != results.Length) + results = ((ObjectMirror)obj).GetValues (fields); + return results [fields.IndexOf (field)]; + } + } + + public void Invalidate () + { + lock (locker) { + results = null; + } + } + } +} diff --git a/VisualPascalABCNETLinux/MonoDebugging/Soft/FieldValueReference.cs b/VisualPascalABCNETLinux/MonoDebugging/Soft/FieldValueReference.cs new file mode 100644 index 000000000..e9ffb5c48 --- /dev/null +++ b/VisualPascalABCNETLinux/MonoDebugging/Soft/FieldValueReference.cs @@ -0,0 +1,213 @@ +// +// FieldValueReference.cs +// +// Authors: Lluis Sanchez Gual +// Jeffrey Stedfast +// +// Copyright (c) 2009 Novell, Inc (http://www.novell.com) +// Copyright (c) 2012 Xamarin Inc. (http://www.xamarin.com) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +using System; +using System.Reflection; +using Mono.Debugging.Evaluation; +using Mono.Debugger.Soft; +using Mono.Debugging.Client; +using System.Collections.Generic; +using System.Linq; + +namespace Mono.Debugging.Soft +{ + public class FieldValueReference: SoftValueReference + { + FieldInfoMirror field; + object obj; + TypeMirror declaringType; + ObjectValueFlags flags; + string vname; + FieldReferenceBatch batch; + + public FieldValueReference (EvaluationContext ctx, FieldInfoMirror field, object obj, TypeMirror declaringType, FieldReferenceBatch batch = null) + : this (ctx, field, obj, declaringType, null, ObjectValueFlags.Field, batch) + { + } + + public FieldValueReference (EvaluationContext ctx, FieldInfoMirror field, object obj, TypeMirror declaringType, string vname, ObjectValueFlags vflags, FieldReferenceBatch batch = null): base (ctx) + { + this.field = field; + this.obj = obj; + this.declaringType = declaringType; + this.vname = vname; + this.batch = batch; + + if (field.IsStatic) + this.obj = null; + + var objectMirror = obj as ObjectMirror; + if (objectMirror != null) + EnsureContextHasDomain (objectMirror.Domain); + + flags = GetFlags (field); + + // if `vflags` already has an origin specified, we need to unset the `Field` flag which is always returned by GetFlags(). + if ((vflags & ObjectValueFlags.OriginMask) != 0) + flags &= ~ObjectValueFlags.Field; + + flags |= vflags; + + if (obj is PrimitiveValue) + flags |= ObjectValueFlags.ReadOnly; + } + + internal static ObjectValueFlags GetFlags (FieldInfoMirror field) + { + var flags = ObjectValueFlags.Field; + + if (field.IsStatic) + flags |= ObjectValueFlags.Global; + + if (field.IsPublic) + flags |= ObjectValueFlags.Public; + else if (field.IsPrivate) + flags |= ObjectValueFlags.Private; + else if (field.IsFamily) + flags |= ObjectValueFlags.Protected; + else if (field.IsFamilyAndAssembly) + flags |= ObjectValueFlags.Internal; + else if (field.IsFamilyOrAssembly) + flags |= ObjectValueFlags.InternalProtected; + + return flags; + } + + public override ObjectValueFlags Flags { + get { + return flags; + } + } + + public override string Name { + get { + return vname ?? field.Name; + } + } + + public override object Type { + get { + return field.FieldType; + } + } + + public override object DeclaringType { + get { + return field.DeclaringType; + } + } + + public override object Value { + get { + if (obj == null) { + // If the type hasn't already been loaded, invoke the .cctor() for types w/ the BeforeFieldInit attribute. + Context.Adapter.ForceLoadType (Context, declaringType); + + return declaringType.GetValue (field, ((SoftEvaluationContext)Context).Thread); + } else if (obj is ObjectMirror) { + if (batch != null) + return batch.GetValue (field); + return ((ObjectMirror)obj).GetValue (field); + } else if (obj is StructMirror) { + StructMirror sm = (StructMirror)obj; + int idx = 0; + foreach (FieldInfoMirror f in sm.Type.GetFields ()) { + if (f.IsStatic) continue; + if (f == field) + break; + idx++; + } + return sm.Fields [idx]; + } else if (obj is StringMirror) { + SoftEvaluationContext cx = (SoftEvaluationContext) Context; + StringMirror val = (StringMirror) obj; + FieldInfo rfield = typeof(string).GetField (field.Name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); + return cx.Session.VirtualMachine.CreateValue (rfield.GetValue (val.Value)); + } else { + SoftEvaluationContext cx = (SoftEvaluationContext) Context; + PrimitiveValue val = (PrimitiveValue) obj; + if (val.Value == null) + return null; + FieldInfo rfield = val.Value.GetType ().GetField (field.Name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); + return cx.Session.VirtualMachine.CreateValue (rfield.GetValue (val.Value)); + } + } + set { + if (obj == null) + declaringType.SetValue (field, (Value)value); + else if (obj is ObjectMirror) { + if (batch != null) + batch.Invalidate (); + ((ObjectMirror)obj).SetValue (field, (Value)value); + } + else if (obj is StructMirror) { + StructMirror sm = (StructMirror)obj; + int idx = 0; + foreach (FieldInfoMirror f in sm.Type.GetFields ()) { + if (f.IsStatic) continue; + if (f == field) + break; + idx++; + } + if (idx != -1) { + sm.Fields [idx] = (Value)value; + // Structs are handled by-value in the debugger, so the source of the object has to be updated + if (ParentSource != null && obj != null) + ParentSource.Value = obj; + } + } + else + throw new NotSupportedException (); + } + } + + internal string [] GetTupleElementNames () + { + return GetTupleElementNames (field.GetCustomAttributes (true)); + } + + internal static string [] GetTupleElementNames (CustomAttributeDataMirror [] attrs) + { + var attr = attrs.FirstOrDefault (at => at.Constructor.DeclaringType.Name == "TupleElementNamesAttribute"); + if (attr == null) + return null; + var attributeValue = attr.ConstructorArguments.Single ().Value; + var array = attributeValue as ArrayMirror; + if (array == null) + return null; + var values = array.GetValues (0, array.Length); + if (!values.Any ()) + return null; + var result = new string [values.Count]; + for (int i = 0; i < result.Length; i++) { + if (values [i] is StringMirror s)//other than string is null + result [i] = s.Value; + } + return result; + } + } +} diff --git a/VisualPascalABCNETLinux/MonoDebugging/Soft/InstructionBreakpoint.cs b/VisualPascalABCNETLinux/MonoDebugging/Soft/InstructionBreakpoint.cs new file mode 100644 index 000000000..7e74e3098 --- /dev/null +++ b/VisualPascalABCNETLinux/MonoDebugging/Soft/InstructionBreakpoint.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Mono.Debugging.Client; + +namespace Mono.Debugging.Soft +{ + [Serializable] + public class InstructionBreakpoint : Breakpoint + { + public string MethodName { get; set; } + public long ILOffset { get; private set; } + + public InstructionBreakpoint (string fileName, int line, int column, long ilOffset) : base (fileName, line, column) + { + this.ILOffset = ilOffset; + } + + } +} diff --git a/VisualPascalABCNETLinux/MonoDebugging/Soft/JsonSourceLink.cs b/VisualPascalABCNETLinux/MonoDebugging/Soft/JsonSourceLink.cs new file mode 100644 index 000000000..654c946f6 --- /dev/null +++ b/VisualPascalABCNETLinux/MonoDebugging/Soft/JsonSourceLink.cs @@ -0,0 +1,9 @@ +using System.Collections.Generic; + +namespace Mono.Debugging.Soft +{ + public class JsonSourceLink + { + public Dictionary Maps { get; set; } + } +} diff --git a/VisualPascalABCNETLinux/MonoDebugging/Soft/LocalVariableBatch.cs b/VisualPascalABCNETLinux/MonoDebugging/Soft/LocalVariableBatch.cs new file mode 100644 index 000000000..715345231 --- /dev/null +++ b/VisualPascalABCNETLinux/MonoDebugging/Soft/LocalVariableBatch.cs @@ -0,0 +1,83 @@ +// +// LocalVariableValueBatch.cs +// +// Author: Jeffrey Stedfast +// +// Copyright (c) 2014 Xamarin Inc. (www.xamarin.com) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +using System; + +using Mono.Debugger.Soft; + +namespace Mono.Debugging.Soft +{ + public class LocalVariableBatch + { + readonly LocalVariable[] variables; + Value[] values; + int version; + + public LocalVariableBatch (LocalVariable[] variables) + { + this.variables = variables; + } + + public Value GetValue (SoftEvaluationContext ctx, LocalVariable variable) + { + if (variable == null) + throw new ArgumentNullException ("variable"); + + if (values == null || version != ctx.Session.StackVersion) { + values = ctx.Frame.GetValues (variables); + version = ctx.Session.StackVersion; + } + + for (int i = 0; i < variables.Length; i++) { + if (variable == variables[i]) + return values[i]; + } + + throw new ArgumentOutOfRangeException ("variable"); + } + + public void SetValue (SoftEvaluationContext ctx, LocalVariable variable, Value value) + { + var canUpdateCache = values != null && version == ctx.Session.StackVersion; + + ctx.Frame.SetValue (variable, value); + ctx.Session.StackVersion++; + + if (canUpdateCache) { + for (int i = 0; i < variables.Length; i++) { + if (variable == variables[i]) { + values[i] = value; + break; + } + } + + version = ctx.Session.StackVersion; + } else { + values = null; + version = 0; + } + } + } +} diff --git a/VisualPascalABCNETLinux/MonoDebugging/Soft/PortablePdbData.cs b/VisualPascalABCNETLinux/MonoDebugging/Soft/PortablePdbData.cs new file mode 100644 index 000000000..df0903264 --- /dev/null +++ b/VisualPascalABCNETLinux/MonoDebugging/Soft/PortablePdbData.cs @@ -0,0 +1,171 @@ +// +// PortablePdbData.cs +// +// Author: +// David Karlaš +// +// Copyright (c) 2017 Xamarin, Inc (http://www.xamarin.com) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +using System; +using Mono.Debugger.Soft; +using System.IO; +using System.Runtime.CompilerServices; +using System.Collections.Generic; +using System.Linq; + +namespace Mono.Debugging.Soft +{ + class PortablePdbData + { + static readonly Guid AsyncMethodSteppingInformationBlob = new Guid ("54FD2AC5-E925-401A-9C2A-F94F171072F8"); + static readonly Guid StateMachineHoistedLocalScopes = new Guid ("6DA9A61E-F8C7-4874-BE62-68BC5630DF71"); + static readonly Guid DynamicLocalVariables = new Guid ("83C563C4-B4F3-47D5-B824-BA5441477EA8"); + static readonly Guid TupleElementNames = new Guid ("ED9FDF71-8879-4747-8ED3-FE5EDE3CE710"); + static readonly Guid DefaultNamespace = new Guid ("58b2eab6-209f-4e4e-a22c-b2d0f910c782"); + static readonly Guid EncLocalSlotMap = new Guid ("755F52A8-91C5-45BE-B4B8-209571E552BD"); + static readonly Guid EncLambdaAndClosureMap = new Guid ("A643004C-0240-496F-A783-30D64F4979DE"); + static readonly Guid SourceLinkGuid = new Guid ("CC110556-A091-4D38-9FEC-25AB9A351A6A"); + static readonly Guid EmbeddedSource = new Guid ("0E8A571B-6926-466E-B4AD-8AB04611F5FE"); + + public static bool IsPortablePdb (string pdbFileName) + { + if (string.IsNullOrEmpty (pdbFileName) || !File.Exists (pdbFileName)) + return false; + using (var file = new FileStream (pdbFileName, FileMode.Open, FileAccess.Read, FileShare.Read)) { + var data = new byte [4]; + int read = file.Read (data, 0, data.Length); + return read == 4 && BitConverter.ToUInt32 (data, 0) == 0x424a5342; + } + } + + readonly byte [] pdbBytes; + private readonly string pdbFileName; + + public PortablePdbData (string pdbFileName) + { + this.pdbFileName = Path.GetFullPath (pdbFileName); + } + + public PortablePdbData (byte [] pdbBytes) + { + this.pdbBytes = pdbBytes ?? throw new ArgumentNullException (nameof(pdbBytes)); + } + + internal class SoftScope + { + public int LiveRangeStart; + + public int LiveRangeEnd; + } + + Stream GetStream() + { + if (pdbBytes != null) + return new MemoryStream (pdbBytes); + return new FileStream (pdbFileName, FileMode.Open, FileAccess.Read, FileShare.Read); + } + + public string GetSourceLinkBlob () + { + /*using (var provider = MetadataReaderProvider.FromPortablePdbStream (GetStream ())) { + var pdbReader = provider.GetMetadataReader (); + + var jsonBlob = + pdbReader.GetCustomDebugInformation (EntityHandle.ModuleDefinition) + .Select (cdiHandle => pdbReader.GetCustomDebugInformation (cdiHandle)) + .Where (cdi => pdbReader.GetGuid (cdi.Kind) == SourceLinkGuid) + .Select (cdi => pdbReader.GetBlobBytes (cdi.Value)) + .FirstOrDefault (); + + if (jsonBlob == null) + return null; + + return System.Text.Encoding.UTF8.GetString (jsonBlob); + }*/ + return null; + } + + // We need proxy method to make sure VS2013/15 doesn't crash(this method won't be called if portable .pdb file doesn't exist, which means 2017+) + [MethodImpl (MethodImplOptions.NoInlining)] + internal SoftScope [] GetHoistedScopes (MethodMirror method) => GetHoistedScopesPrivate (method); + + internal SoftScope [] GetHoistedScopesPrivate (MethodMirror method) + { + /*using (var metadataReader = MetadataReaderProvider.FromPortablePdbStream (GetStream ())) { + var reader = metadataReader.GetMetadataReader (); + var methodHandle = MetadataTokens.MethodDefinitionHandle (method.MetadataToken); + var customDebugInfos = reader.GetCustomDebugInformation (methodHandle); + foreach (var item in customDebugInfos) { + var debugInfo = reader.GetCustomDebugInformation (item); + if (reader.GetGuid (debugInfo.Kind) == StateMachineHoistedLocalScopes) { + var bytes = reader.GetBlobBytes (debugInfo.Value); + var result = new SoftScope [bytes.Length / 8]; + for (int i = 0; i < bytes.Length; i += 8) { + var offset = BitConverter.ToInt32 (bytes, i); + var len = BitConverter.ToInt32 (bytes, i + 4); + result [i / 8] = new SoftScope () { + LiveRangeStart = offset, + LiveRangeEnd = offset + len + }; + } + return result; + } + } + }*/ + return null; + } + + // We need proxy method to make sure VS2013/15 doesn't crash(this method won't be called if portable .pdb file doesn't exist, which means 2017+) + [MethodImpl (MethodImplOptions.NoInlining)] + internal string [] GetTupleElementNames (MethodMirror method, int localVariableIndex) => TupleElementNamesPrivate (method, localVariableIndex); + + /*private static string [] DecodeTupleElementNames (BlobReader reader) + { + var list = new List (); + while (reader.RemainingBytes > 0) { + int byteCount = reader.IndexOf (0); + string value = reader.ReadUTF8 (byteCount); + byte terminator = reader.ReadByte (); + list.Add (value.Length == 0 ? null : value); + } + return list.ToArray (); + }*/ + + internal string [] TupleElementNamesPrivate (MethodMirror method, int localVariableIndex) + { + /*using (var metadataReader = MetadataReaderProvider.FromPortablePdbStream (GetStream())) { + var reader = metadataReader.GetMetadataReader (); + var methodHandle = MetadataTokens.MethodDefinitionHandle (method.MetadataToken); + var localScopes = reader.GetLocalScopes (methodHandle); + // localVariableIndex is not really il_index, but sequential index when fetching locals + // hence use Skip(index) instead of Index matching. + var localVar = localScopes.Select (s => reader.GetLocalScope (s)).SelectMany (s => s.GetLocalVariables ()).Skip (localVariableIndex).First (); + var customDebugInfos = reader.GetCustomDebugInformation (localVar); + foreach (var item in customDebugInfos) { + var debugInfo = reader.GetCustomDebugInformation (item); + if (reader.GetGuid (debugInfo.Kind) == TupleElementNames) { + return DecodeTupleElementNames (reader.GetBlobReader (debugInfo.Value)); + } + } + }*/ + return null; + } + } +} diff --git a/VisualPascalABCNETLinux/MonoDebugging/Soft/PropertyValueReference.cs b/VisualPascalABCNETLinux/MonoDebugging/Soft/PropertyValueReference.cs new file mode 100644 index 000000000..7999a486c --- /dev/null +++ b/VisualPascalABCNETLinux/MonoDebugging/Soft/PropertyValueReference.cs @@ -0,0 +1,182 @@ +// +// PropertyValueReference.cs +// +// Author: +// Lluis Sanchez Gual +// +// Copyright (c) 2009 Novell, Inc (http://www.novell.com) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +using Mono.Debugging.Evaluation; +using Mono.Debugger.Soft; +using Mono.Debugging.Client; +using System; +using System.Collections.Generic; + +namespace Mono.Debugging.Soft +{ + public class PropertyValueReference: SoftValueReference + { + PropertyInfoMirror property; + TypeMirror declaringType; + ObjectValueFlags flags; + MethodMirror getter; + Value[] indexerArgs; + object obj, value; + bool haveValue; + bool safe; + + public PropertyValueReference (EvaluationContext ctx, PropertyInfoMirror property, object obj, TypeMirror declaringType, MethodMirror getter, Value[] indexerArgs): base (ctx) + { + this.safe = Context.Adapter.IsSafeToInvokeMethod (ctx, getter, obj ?? declaringType); + this.declaringType = declaringType; + this.indexerArgs = indexerArgs; + this.property = property; + this.getter = getter; + this.obj = obj; + + if (getter.IsStatic) + this.obj = null; + + var objectMirror = obj as ObjectMirror; + if (objectMirror != null) + EnsureContextHasDomain (objectMirror.Domain); + + flags = GetFlags (property, getter); + } + + internal static ObjectValueFlags GetFlags (PropertyInfoMirror property, MethodMirror getter) + { + var flags = ObjectValueFlags.Property; + + if (property.GetSetMethod (true) == null) + flags |= ObjectValueFlags.ReadOnly; + + if (getter.IsStatic) + flags |= ObjectValueFlags.Global; + + if (getter.IsPublic) + flags |= ObjectValueFlags.Public; + else if (getter.IsPrivate) + flags |= ObjectValueFlags.Private; + else if (getter.IsFamily) + flags |= ObjectValueFlags.Protected; + else if (getter.IsFamilyAndAssembly) + flags |= ObjectValueFlags.Internal; + else if (getter.IsFamilyOrAssembly) + flags |= ObjectValueFlags.InternalProtected; + + if (property.DeclaringType.IsValueType) + flags |= ObjectValueFlags.ReadOnly; // Setting property values on structs is not supported by sdb + + return flags; + } + + public override ObjectValueFlags Flags { + get { + return flags; + } + } + + public override string Name { + get { + return property.Name; + } + } + + public override object Type { + get { + return property.PropertyType; + } + } + + public override object DeclaringType { + get { + return property.DeclaringType; + } + } + + public override object Value { + get { + return GetValue (Context); + } + set { + SetValue (Context, value); + } + } + + public override object GetValue (EvaluationContext ctx) + { + if (!haveValue) { + if (!safe) + throw new EvaluatorException ("This property is not safe to evaluate."); + + value = ((SoftEvaluationContext) ctx).RuntimeInvoke (getter, obj ?? declaringType, indexerArgs); + haveValue = true; + } + + return value; + } + + public override void SetValue (EvaluationContext ctx, object value) + { + ctx.AssertTargetInvokeAllowed (); + + var args = new Value [indexerArgs != null ? indexerArgs.Length + 1 : 1]; + if (indexerArgs != null) + indexerArgs.CopyTo (args, 0); + + args [args.Length - 1] = (Value) value; + + var setter = property.GetSetMethod (true); + if (setter == null) + throw new EvaluatorException ("Property is read-only"); + + this.value = null; + haveValue = false; + + ((SoftEvaluationContext) ctx).RuntimeInvoke (setter, obj ?? declaringType, args); + + this.value = value; + haveValue = true; + } + + protected override bool CanEvaluate (EvaluationOptions options) + { + if (options.AllowTargetInvoke) + return safe; + + if (!safe) + return false; + + try { + GetValue (Context.WithOptions (options)); + return true; + } catch (ImplicitEvaluationDisabledException) { + return false; + } + } + + internal string [] GetTupleElementNames () + { + return FieldValueReference.GetTupleElementNames (property.GetCustomAttributes (true)); + } + } +} diff --git a/VisualPascalABCNETLinux/MonoDebugging/Soft/SoftDebuggerAdaptor.cs b/VisualPascalABCNETLinux/MonoDebugging/Soft/SoftDebuggerAdaptor.cs new file mode 100644 index 000000000..84cf0ba3f --- /dev/null +++ b/VisualPascalABCNETLinux/MonoDebugging/Soft/SoftDebuggerAdaptor.cs @@ -0,0 +1,2706 @@ +// +// SoftDebuggerAdaptor.cs +// +// Authors: Lluis Sanchez Gual +// Jeffrey Stedfast +// +// Copyright (c) 2009 Novell, Inc (http://www.novell.com) +// Copyright (c) 2011,2012 Xamain Inc. (http://www.xamarin.com) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +using System; +using System.Linq; +using System.Diagnostics; +using System.Reflection; +using System.Reflection.Emit; +using System.Collections.Generic; +using System.Runtime.CompilerServices; +using System.Threading; + +using Mono.Debugger.Soft; +using Mono.Debugging.Backend; +using Mono.Debugging.Evaluation; +using Mono.Debugging.Client; + + +namespace Mono.Debugging.Soft +{ + public class SoftDebuggerAdaptor : ObjectValueAdaptor + { + static readonly Dictionary convertOps = new Dictionary (); + delegate object TypeCastDelegate (object value); + + static SoftDebuggerAdaptor () + { + convertOps.Add (typeof (double), OpCodes.Conv_R8); + convertOps.Add (typeof (float), OpCodes.Conv_R4); + convertOps.Add (typeof (ulong), OpCodes.Conv_U8); + convertOps.Add (typeof (uint), OpCodes.Conv_U4); + convertOps.Add (typeof (ushort), OpCodes.Conv_U2); + convertOps.Add (typeof (char), OpCodes.Conv_U2); + convertOps.Add (typeof (byte), OpCodes.Conv_U1); + convertOps.Add (typeof (long), OpCodes.Conv_I8); + convertOps.Add (typeof (int), OpCodes.Conv_I4); + convertOps.Add (typeof (short), OpCodes.Conv_I2); + convertOps.Add (typeof (sbyte), OpCodes.Conv_I1); + } + + public SoftDebuggerAdaptor () + { + } + + public SoftDebuggerSession Session { + get { return (SoftDebuggerSession)DebuggerSession; } + set { DebuggerSession = value; } + } + + //static string GetPrettyMethodName (EvaluationContext ctx, MethodMirror method) + //{ + // var name = new System.Text.StringBuilder (); + + // name.Append (ctx.Adapter.GetDisplayTypeName (method.ReturnType.FullName)); + // name.Append (" "); + // name.Append (ctx.Adapter.GetDisplayTypeName (method.DeclaringType.FullName)); + // name.Append ("."); + // name.Append (method.Name); + + // if (method.VirtualMachine.Version.AtLeast (2, 12)) { + // if (method.IsGenericMethodDefinition || method.IsGenericMethod) { + // name.Append ("<"); + // if (method.VirtualMachine.Version.AtLeast (2, 15)) { + // var argTypes = method.GetGenericArguments (); + // for (int i = 0; i < argTypes.Length; i++) { + // if (i != 0) + // name.Append (", "); + // name.Append (ctx.Adapter.GetDisplayTypeName (argTypes[i].FullName)); + // } + // } + // name.Append (">"); + // } + // } + + // name.Append (" ("); + // var @params = method.GetParameters (); + // for (int i = 0; i < @params.Length; i++) { + // if (i != 0) + // name.Append (", "); + // if (@params[i].Attributes.HasFlag (ParameterAttributes.Out)) { + // if (@params[i].Attributes.HasFlag (ParameterAttributes.In)) + // name.Append ("ref "); + // else + // name.Append ("out "); + // } + // name.Append (ctx.Adapter.GetDisplayTypeName (@params[i].ParameterType.FullName)); + // name.Append (" "); + // name.Append (@params[i].Name); + // } + // name.Append (")"); + + // return name.ToString (); + //} + + string InvokeToString (SoftEvaluationContext ctx, MethodMirror method, object obj) + { + try { + var result = ctx.RuntimeInvoke (method, obj, new Value[0]); + if (result is StringMirror str) + return MirrorStringToString (ctx, str); + + return null; + } catch { + return GetDisplayTypeName (GetValueTypeName (ctx, obj)); + } + } + + public override string CallToString (EvaluationContext ctx, object obj) + { + if (obj == null) + return null; + + if (obj is StringMirror str) + return str.Value; + + if (obj is EnumMirror em) + return em.StringValue; + + if (obj is PrimitiveValue primitive) + return primitive.Value.ToString (); + + if (obj is PointerValue pointer) + return string.Format ("0x{0:x}", pointer.Address); + + var cx = (SoftEvaluationContext) ctx; + var sm = obj as StructMirror; + var om = obj as ObjectMirror; + + if (sm != null && sm.Type.IsPrimitive) { + // Boxed primitive + if (sm.Fields.Length > 0 && (sm.Fields[0] is PrimitiveValue)) + return ((PrimitiveValue) sm.Fields[0]).Value.ToString (); + } else if (om != null && cx.Options.AllowTargetInvoke) { + var method = OverloadResolve (cx, om.Type, "ToString", null, new ArgumentType[0], true, false, false); + if (method != null && method.DeclaringType.FullName != "System.Object") + return InvokeToString (cx, method, obj); + } else if (sm != null && cx.Options.AllowTargetInvoke) { + var method = OverloadResolve (cx, sm.Type, "ToString", null, new ArgumentType [0], true, false, false); + if (method != null && method.DeclaringType.FullName != "System.ValueType") + return InvokeToString (cx, method, obj); + } + + return GetDisplayTypeName (GetValueTypeName (ctx, obj)); + } + + public override object TryConvert (EvaluationContext ctx, object obj, object targetType) + { + var res = TryCast (ctx, obj, targetType); + + if (res != null || obj == null) + return res; + + var otype = GetValueType (ctx, obj) as Type; + + if (otype != null) { + var tm = targetType as TypeMirror; + if (tm != null) + targetType = Type.GetType (tm.FullName, false); + + var tt = targetType as Type; + if (tt != null) { + try { + if (obj is PrimitiveValue primitive) + obj = primitive.Value; + + res = System.Convert.ChangeType (obj, tt); + return CreateValue (ctx, res); + } catch { + return null; + } + } + } + + return null; + } + + static readonly Dictionary typeCastDelegatesCache = new Dictionary (); + + static TypeCastDelegate GenerateTypeCastDelegate (string methodName, Type fromType, Type toType) + { + lock(typeCastDelegatesCache) { + if (typeCastDelegatesCache.TryGetValue (methodName, out var cached)) + return cached; + + var argTypes = new [] { typeof (object) }; + var method = new DynamicMethod (methodName, typeof (object), argTypes, true); + var il = method.GetILGenerator (); + ConstructorInfo ctorInfo; + System.Reflection.MethodInfo methodInfo; + + il.Emit (OpCodes.Ldarg_0); + il.Emit (OpCodes.Unbox_Any, fromType); + + if (fromType.IsSubclassOf (typeof (Nullable))) { + var propInfo = fromType.GetProperty ("Value"); + methodInfo = propInfo.GetGetMethod (); + + il.Emit (OpCodes.Stloc_0); + il.Emit (OpCodes.Ldloca_S); + il.Emit (OpCodes.Call, methodInfo); + + fromType = methodInfo.ReturnType; + } + + if (!convertOps.TryGetValue (toType, out var conv)) { + argTypes = new [] { fromType }; + + if (toType == typeof (string)) { + methodInfo = fromType.GetMethod ("ToString", new Type [0]); + il.Emit (OpCodes.Call, methodInfo); + } else if ((methodInfo = toType.GetMethod ("op_Explicit", argTypes)) != null) { + il.Emit (OpCodes.Call, methodInfo); + } else if ((methodInfo = toType.GetMethod ("op_Implicit", argTypes)) != null) { + il.Emit (OpCodes.Call, methodInfo); + } else if ((ctorInfo = toType.GetConstructor (argTypes)) != null) { + il.Emit (OpCodes.Call, ctorInfo); + } else { + // No idea what else to try... + throw new InvalidCastException (); + } + } else { + il.Emit (conv); + } + + il.Emit (OpCodes.Box, toType); + il.Emit (OpCodes.Ret); + cached = (TypeCastDelegate)method.CreateDelegate (typeof (TypeCastDelegate)); + typeCastDelegatesCache [methodName] = cached; + + return cached; + } + } + + static object DynamicCast (object value, Type target) + { + var methodName = string.Format ("CastFrom{0}To{1}", value.GetType ().Name, target.Name); + var method = GenerateTypeCastDelegate (methodName, value.GetType (), target); + + return method.Invoke (value); + } + + static bool CanForceCast (EvaluationContext ctx, ArgumentType fromType, TypeMirror toType) + { + var cx = (SoftEvaluationContext) ctx; + MethodMirror method; + + if (CanCast (ctx, fromType, toType)) + return true; + + // check for explicit cast operators in the target type + method = OverloadResolve (cx, toType, "op_Explicit", null, new [] { fromType }, false, true, false, false); + if (method != null) + return true; + + // check for explicit cast operators on the source type + method = OverloadResolve (cx, fromType.Type, "op_Explicit", null, toType, new [] { fromType }, false, true, false, false); + if (method != null) + return true; + + method = OverloadResolve (cx, toType, ".ctor", null, new [] { fromType }, true, false, false, false); + if (method != null) + return true; + + return false; + } + + static bool CanCast (EvaluationContext ctx, ArgumentType fromType, TypeMirror toType) + { + var cx = (SoftEvaluationContext)ctx; + MethodMirror method; + + // check for implicit cast operators in the target type + method = OverloadResolve (cx, toType, "op_Implicit", null, new [] { fromType }, false, true, false, false); + if (method != null) + return true; + + // check for implicit cast operators on the source type + method = OverloadResolve (cx, fromType.Type, "op_Implicit", null, toType, new [] { fromType }, false, true, false, false); + if (method != null) + return true; + + return false; + } + + object TryForceCast (EvaluationContext ctx, Value value, TypeMirror fromType, TypeMirror toType) + { + var cx = (SoftEvaluationContext) ctx; + MethodMirror method; + + // check for explicit and implicit cast operators in the target type + method = OverloadResolve (cx, toType, "op_Explicit", null, new [] { new ArgumentType { Type = fromType } }, false, true, false, false); + if (method != null) + return cx.RuntimeInvoke (method, toType, new [] { value }); + + method = OverloadResolve (cx, toType, "op_Implicit", null, new [] { new ArgumentType { Type = fromType } }, false, true, false, false); + if (method != null) + return cx.RuntimeInvoke (method, toType, new [] { value }); + + // check for explicit and implicit cast operators on the source type + method = OverloadResolve (cx, fromType, "op_Explicit", null, toType, new [] { new ArgumentType { Type = fromType } }, false, true, false, false); + if (method != null) + return cx.RuntimeInvoke (method, fromType, new [] { value }); + + method = OverloadResolve (cx, fromType, "op_Implicit", null, toType, new [] { new ArgumentType { Type = fromType } }, false, true, false, false); + if (method != null) + return cx.RuntimeInvoke (method, fromType, new [] { value }); + + // Finally, try a ctor... + try { + return CreateValue (ctx, toType, value); + } catch { + return null; + } + } + + public override object TryCast (EvaluationContext ctx, object val, object type) + { + var cx = (SoftEvaluationContext) ctx; + var toType = type as TypeMirror; + TypeMirror fromType; + + if (val == null) + return null; + + if (val is DelayedLambdaValue) + return null; + + var valueType = GetValueType (ctx, val); + + fromType = valueType as TypeMirror; + + // If we are trying to cast into non-primitive/enum value(e.g. System.nint) + // that class might have implicit operator and this must be handled via TypeMirrors + if (toType != null && !toType.IsPrimitive && !toType.IsEnum) + fromType = ToTypeMirror (ctx, valueType); + + if (fromType != null) { + // Try casting the primitive type of the enum + if (val is EnumMirror em) + return TryCast (ctx, CreateValue (ctx, em.Value), type); + + if (toType != null && toType.IsAssignableFrom(fromType)) + return val; + + if (toType == null) + return null; + + MethodMirror method; + + if (fromType.IsGenericType && fromType.FullName.StartsWith ("System.Nullable`1", StringComparison.Ordinal)) { + method = OverloadResolve (cx, fromType, "get_Value", null, new ArgumentType[0], true, false, false); + if (method != null) { + val = cx.RuntimeInvoke (method, val, new Value[0]); + return TryCast (ctx, val, type); + } + } + + + if (toType.IsGenericType && toType.FullName.StartsWith ("System.Nullable`1", StringComparison.Ordinal)) { + if (val is PrimitiveValue primitiveVal && primitiveVal.Value == null) { + val = CreateValue (ctx, toType, new object [0]); + } else { + val = CreateValue (ctx, toType, val); + } + return val; + } + + return TryForceCast (ctx, (Value) val, fromType, toType); + } + + var vtype = valueType as Type; + + if (vtype != null) { + if (toType != null) { + if (toType.IsEnum) { + var casted = TryCast (ctx, val, toType.EnumUnderlyingType) as PrimitiveValue; + + return casted != null ? cx.Session.VirtualMachine.CreateEnumMirror (toType, casted) : null; + } + + type = Type.GetType (toType.FullName, false); + } + + var tt = type as Type; + if (tt != null) { + if (tt.IsAssignableFrom (vtype)) + return val; + + try { + if (tt.IsPrimitive || tt == typeof (string)) { + if (val is PrimitiveValue primitive) + val = primitive.Value; + + if (val == null) + return null; + + return CreateValue (ctx, DynamicCast (val, tt)); + } + + fromType = (TypeMirror) ForceLoadType (ctx, ((Type) valueType).FullName); + if (toType == null) + toType = (TypeMirror) ForceLoadType (ctx, tt.FullName); + + return TryForceCast (ctx, (Value) val, fromType, toType); + } catch { + return null; + } + } + } + + return null; + } + + public override IStringAdaptor CreateStringAdaptor (EvaluationContext ctx, object str) + { + return new StringAdaptor ((StringMirror) str); + } + + public override ICollectionAdaptor CreateArrayAdaptor (EvaluationContext ctx, object arr) + { + return new ArrayAdaptor ((ArrayMirror) arr); + } + + public override object CreateNullValue (EvaluationContext ctx, object type) + { + var cx = (SoftEvaluationContext) ctx; + + return new PrimitiveValue (cx.Session.VirtualMachine, null); + } + + public override object CreateTypeObject (EvaluationContext ctx, object type) + { + return ((TypeMirror) type).GetTypeObject (); + } + + public override object CreateValue (EvaluationContext ctx, object type, params object[] argValues) + { + ctx.AssertTargetInvokeAllowed (); + + var cx = (SoftEvaluationContext) ctx; + var tm = (TypeMirror) type; + + var types = new ArgumentType [argValues.Length]; + var values = new Value[argValues.Length]; + + for (int n = 0; n < argValues.Length; n++) { + types[n] = ToArgumentType (ctx, GetValueType (ctx, argValues[n])); + } + + var method = OverloadResolve (cx, tm, ".ctor", null, types, true, false, false); + + if (method != null) { + var mparams = method.GetParameters (); + + for (int n = 0; n < argValues.Length; n++) { + var param_type = mparams [n].ParameterType; + + if (param_type.FullName != types [n].Type.FullName && !param_type.IsAssignableFrom (types [n].Type) && param_type.IsGenericType) { + /* TODO: Add genericTypeArgs and handle this + bool throwCastException = true; + + if (method.VirtualMachine.Version.AtLeast (2, 15)) { + var args = param_type.GetGenericArguments (); + + if (args.Length == genericTypes.Length) { + var real_type = soft.Adapter.GetType (soft, param_type.GetGenericTypeDefinition ().FullName, genericTypes); + + values [n] = (Value)TryCast (soft, (Value)argValues [n], real_type); + if (!(values [n] == null && argValues [n] != null && !soft.Adapter.IsNull (soft, argValues [n]))) + throwCastException = false; + } + } + + if (throwCastException) { + string fromType = !IsGeneratedType (types [n]) ? soft.Adapter.GetDisplayTypeName (soft, types [n]) : types [n].FullName; + string toType = soft.Adapter.GetDisplayTypeName (soft, param_type); + + throw new EvaluatorException ("Argument {0}: Cannot implicitly convert `{1}' to `{2}'", n, fromType, toType); + }*/ + } else if (param_type.FullName != types [n].Type.FullName && !param_type.IsAssignableFrom (types [n].Type) && CanForceCast (ctx, types [n], param_type)) { + values [n] = (Value)TryCast (ctx, argValues [n], param_type); + } else if (param_type.FullName != types [n].Type.FullName && !param_type.IsAssignableFrom (types [n].Type) && CanDoPrimaryCast (types [n], param_type)) { + values [n] = (Value)TryConvert (ctx, argValues [n], param_type); + } else { + values [n] = (Value)argValues [n]; + } + } + + lock(method.VirtualMachine) { + return tm.NewInstance (cx.Thread, method, values); + } + } + + if (argValues.Length == 0 && tm.VirtualMachine.Version.AtLeast (2, 31)) + return tm.NewInstance (); + + var typeName = ctx.Adapter.GetDisplayTypeName (ctx, type); + + throw new EvaluatorException ("Constructor not found for type `{0}'.", typeName); + } + + public override object CreateValue (EvaluationContext ctx, object value) + { + var cx = (SoftEvaluationContext) ctx; + + if (value is string str) + return cx.Domain.CreateString (str); + + if (value is decimal) { + var bits = decimal.GetBits ((decimal)value); + + return CreateValue (ctx, ToTypeMirror (ctx, typeof (decimal)), CreateValue (ctx, bits [0]), CreateValue (ctx, bits [1]), CreateValue (ctx, bits [2]), CreateValue (ctx, (bits [3] & unchecked((int)0x80000000)) != 0), CreateValue (ctx, (byte)(bits [3] >> 16))); + } + + return cx.Session.VirtualMachine.CreateValue (value); + } + + public object CreateByteArray (EvaluationContext ctx, byte [] bytes) + { + var cx = (SoftEvaluationContext) ctx; + + if (Session.VirtualMachine.Version.AtLeast (2, 52)) + return cx.Domain.CreateByteArray (bytes); + + var arrayType = GetType (ctx, "System.Array"); + var int32Type = GetType (ctx, "System.Int32"); + var typeType = GetType (ctx, "System.Type"); + var stringType = GetType (ctx, "System.String"); + var byteTypeValue = RuntimeInvoke (ctx, typeType, null, "GetType", new object [] { stringType }, new object [] { CreateValue (ctx, "System.Byte") }); + //var byteType = ctx.Adapter.GetType (ctx, "System.Byte"); + var n = CreateValue (ctx, bytes.Length); + var args = new object [] { byteTypeValue, n }; + var arr = RuntimeInvoke (ctx, arrayType, null, "CreateInstance", new object [] { typeType, int32Type }, args); + if (arr is ArrayMirror) { + var arrm = arr as ArrayMirror; + arrm.SetByteValues (bytes); + return arr; + } + + return null; + } + + public override object GetBaseValue (EvaluationContext ctx, object val) + { + return val; + } + + public override bool NullableHasValue (EvaluationContext ctx, object type, object obj) + { + var hasValue = GetMember (ctx, type, obj, "hasValue") ?? GetMember (ctx, type, obj, "has_value"); + + return (bool) hasValue.ObjectValue; + } + + public override ValueReference NullableGetValue (EvaluationContext ctx, object type, object obj) + { + return GetMember (ctx, type, obj, "value"); + } + + public override object GetEnclosingType (EvaluationContext ctx) + { + return ((SoftEvaluationContext) ctx).Frame.Method.DeclaringType; + } + + public override string[] GetImportedNamespaces (EvaluationContext ctx) + { + var namespaces = new HashSet (); + var cx = (SoftEvaluationContext) ctx; + + foreach (var type in cx.Session.GetAllTypes ()) + namespaces.Add (type.Namespace); + + var nss = new string [namespaces.Count]; + namespaces.CopyTo (nss); + + return nss; + } + + public override ValueReference GetIndexerReference (EvaluationContext ctx, object target, object type, object [] indices) + { + var values = new Value [indices.Length]; + var types = new ArgumentType [indices.Length]; + for (int n = 0; n < indices.Length; n++) { + types[n] = ToArgumentType (ctx, GetValueType (ctx, indices[n])); + values[n] = (Value) indices[n]; + } + + var candidates = new List (); + var props = new List (); + var mirType = type as TypeMirror; + while (mirType != null) { + foreach (var prop in mirType.GetProperties ()) { + var met = prop.GetGetMethod (true); + if (met != null && + !met.IsStatic && + met.GetParameters ().Length > 0 && + !(met.IsPrivate && met.IsVirtual)) {//Don't use explicit interface implementation + candidates.Add (met); + props.Add (prop); + } + } + mirType = mirType.BaseType; + } + + var idx = OverloadResolve ((SoftEvaluationContext) ctx, mirType, null, null, null, types, candidates, true); + int i = candidates.IndexOf (idx); + + var getter = props[i].GetGetMethod (true); + + return getter != null ? new PropertyValueReference (ctx, props[i], target, null, getter, values) : null; + } + + public override ValueReference GetIndexerReference (EvaluationContext ctx, object target, object[] indices) + { + object valueType = GetValueType (ctx, target); + var targetType = valueType as TypeMirror; + + if (targetType == null) { + var tt = valueType as Type; + + if (tt == null) + return null; + + targetType = (TypeMirror) ForceLoadType (ctx, tt.FullName); + } + + return GetIndexerReference (ctx, target, targetType, indices); + } + + static bool InGeneratedClosureOrIteratorType (EvaluationContext ctx) + { + var cx = (SoftEvaluationContext) ctx; + + if (cx.Frame.Method.IsStatic) + return false; + + var tm = cx.Frame.Method.DeclaringType; + + return IsGeneratedType (tm); + } + + static bool IsLocalFunction(EvaluationContext ctx) + { + return ((SoftEvaluationContext)ctx).Frame.Method.Name.IndexOf (">g__", StringComparison.Ordinal) > 0; + } + + internal static bool IsGeneratedType (TypeMirror tm) + { + // + // This should cover all C# generated special containers + // - anonymous methods + // - lambdas + // - iterators + // - async methods + // + // which allow stepping into + // + + // Note: mcs uses the form <${NAME}>c__${KIND}${NUMBER} where the leading '<' seems to have been dropped in 3.4.x + // csc uses the form <${NAME}>d__${NUMBER} + // roslyn uses the form <${NAME}>d + + return tm.Name.IndexOf (">c__", StringComparison.Ordinal) > 0 || tm.Name.IndexOf (">d", StringComparison.Ordinal) > 0; + } + + internal static string GetNameFromGeneratedType (TypeMirror tm) + { + return tm.Name.Substring (1, tm.Name.IndexOf ('>') - 1); + } + + static bool IsHoistedThisReference (FieldInfoMirror field) + { + // mcs is "<>f__this" or "$this" (if in an async compiler generated type) + // csc is "<>4__this" + return field.Name == "$this" || + (field.Name.StartsWith ("<>", StringComparison.Ordinal) && + field.Name.EndsWith ("__this", StringComparison.Ordinal)); + } + + static bool IsClosureReferenceField (FieldInfoMirror field) + { + // mcs is "$locvar" + // old mcs is "<>f__ref" + // csc is "CS$<>" + // roslyn is "<>8__" + return field.Name.StartsWith ("CS$<>", StringComparison.Ordinal) || + field.Name.StartsWith ("<>f__ref", StringComparison.Ordinal) || + field.Name.StartsWith ("$locvar", StringComparison.Ordinal) || + field.Name.StartsWith ("<>8__", StringComparison.Ordinal); + } + + static bool IsClosureReferenceLocal (LocalVariable local) + { + if (local.Name == null) + return false; + + // mcs is "$locvar" or starts with '<' + // csc is "CS$<>" + return local.Name.Length == 0 || local.Name[0] == '<' || local.Name.StartsWith ("$locvar", StringComparison.Ordinal) || + local.Name.StartsWith ("CS$<>", StringComparison.Ordinal); + } + + static bool IsGeneratedTemporaryLocal (LocalVariable local) + { + // csc uses CS$ prefix for temporary variables and <>t__ prefix for async task-related state variables + return local.Name != null && (local.Name.StartsWith ("CS$", StringComparison.Ordinal) || local.Name.StartsWith ("<>t__", StringComparison.Ordinal)); + } + + Dictionary methodScopeCache = new Dictionary (); + + string GetHoistedIteratorLocalName (FieldInfoMirror field, SoftEvaluationContext cx) + { + //mcs captured args, of form <$>name + if (field.Name.StartsWith ("<$>", StringComparison.Ordinal)) { + return field.Name.Substring (3); + } + + // csc, mcs locals of form __#, where # represents index of scope + // roslyn locals of form 5__#, where # represents index of scope + if (field.Name [0] == '<') { + int suffixLength = 3; + var i = field.Name.IndexOf (">__", StringComparison.Ordinal); + if (i == -1) { + suffixLength = 4; + i = field.Name.IndexOf (">5__", StringComparison.Ordinal); + } + + if (i != -1 && field.VirtualMachine.Version.AtLeast (2, 43)) { + if (int.TryParse (field.Name.Substring (i + suffixLength), out int scopeIndex) && scopeIndex > 0) {//0 means whole method scope + PortablePdbData.SoftScope [] scopes = null; + + scopeIndex--;//Scope index is 1 based(not zero) + + if (cx.Frame.Method != null && !methodScopeCache.TryGetValue (cx.Frame.Method, out scopes)) { + scopes = cx.Session.GetPdbData (cx.Frame.Method)?.GetHoistedScopes (cx.Frame.Method); + if (scopes == null || scopes.Length == 0) { + // If hoisted scopes are empty use normal scopes + scopes = cx.Frame.Method.GetScopes ().Select (s => new PortablePdbData.SoftScope { LiveRangeStart = s.LiveRangeStart, LiveRangeEnd = s.LiveRangeEnd }).ToArray (); + DebuggerLoggingService.LogMessage ("PDB data not found for frame: {0}", cx.Frame); + } + + methodScopeCache [cx.Frame.Method] = scopes; + } + + if (scopes != null && scopeIndex < scopes.Length) { + var scope = scopes [scopeIndex]; + if (scope.LiveRangeStart > cx.Frame.Location.ILOffset || scope.LiveRangeEnd < cx.Frame.Location.ILOffset) + return null; + } + } + } + + i = field.Name.IndexOf ('>'); + if (i > 1) { + return field.Name.Substring (1, i - 1); + } + } + + return null; + } + + IEnumerable GetHoistedLocalVariables (SoftEvaluationContext cx, ValueReference vthis, HashSet alreadyVisited = null) + { + if (vthis == null) + return new ValueReference [0]; + + object val; + try { + val = vthis.Value; + } catch (InvalidStackFrameException) { + return new ValueReference [0]; + } catch (AbsentInformationException) { + return new ValueReference [0]; + } catch (EvaluatorException ex) when (ex.InnerException is AbsentInformationException) { + return new ValueReference [0]; + } + + if (IsNull (cx, val)) + return new ValueReference [0]; + + var tm = (TypeMirror) vthis.Type; + var isIterator = IsGeneratedType (tm); + + var list = new List (); + var type = (TypeMirror) vthis.Type; + + foreach (var field in type.GetFields ()) { + if (IsHoistedThisReference (field)) + continue; + + if (IsClosureReferenceField (field)) { + alreadyVisited = alreadyVisited ?? new HashSet (); + if (alreadyVisited.Contains (field)) + continue; + alreadyVisited.Add (field); + list.AddRange (GetHoistedLocalVariables (cx, new FieldValueReference (cx, field, val, type), alreadyVisited)); + continue; + } + + if (field.Name[0] == '<') { + if (isIterator) { + var name = GetHoistedIteratorLocalName (field, cx); + + if (!string.IsNullOrEmpty (name)) + list.Add (new FieldValueReference (cx, field, val, type, name, ObjectValueFlags.Variable) { + ParentSource = vthis + }); + } + } else if (!field.Name.Contains ("$")) { + list.Add (new FieldValueReference (cx, field, val, type, field.Name, ObjectValueFlags.Variable) { + ParentSource = vthis + }); + } + } + + return list; + } + + ValueReference GetHoistedThisReference (SoftEvaluationContext cx) + { + try { + var val = cx.Frame.GetThis (); + var valueType = GetValueType (cx, val); + var type = valueType as TypeMirror; + + if (type == null) { + var tt = valueType as Type; + + if (tt == null) + return null; + + type = (TypeMirror) ForceLoadType (cx, tt.FullName); + } + + return GetHoistedThisReference (cx, type, val); + } catch (InvalidStackFrameException) { + } catch (AbsentInformationException) { + } + return null; + } + + ValueReference GetHoistedThisReference (SoftEvaluationContext cx, TypeMirror type, object val, HashSet alreadyVisited = null) + { + foreach (var field in type.GetFields ()) { + if (IsHoistedThisReference (field)) + return new FieldValueReference (cx, field, val, type, "this", ObjectValueFlags.Literal); + + if (IsClosureReferenceField (field)) { + alreadyVisited = alreadyVisited ?? new HashSet (); + if (alreadyVisited.Contains (field)) + continue; + alreadyVisited.Add (field); + var fieldRef = new FieldValueReference (cx, field, val, type); + var thisRef = GetHoistedThisReference (cx, field.FieldType, fieldRef.Value, alreadyVisited); + if (thisRef != null) + return thisRef; + } + } + + return null; + } + + // if the local does not have a name, constructs one from the index + static string GetLocalName (SoftEvaluationContext cx, LocalVariable local) + { + if (!string.IsNullOrEmpty (local.Name) || cx.SourceCodeAvailable) + return local.Name; + + return "loc" + local.Index; + } + + protected override ValueReference OnGetLocalVariable (EvaluationContext ctx, string name) + { + var cx = (SoftEvaluationContext) ctx; + + if (InGeneratedClosureOrIteratorType (cx) || IsLocalFunction(cx)) + return FindByName (OnGetLocalVariables (cx), v => v.Name, name, ctx.CaseSensitive); + + try { + LocalVariable local = null; + + if (!cx.SourceCodeAvailable) { + if (name.StartsWith ("loc", StringComparison.Ordinal)) { + int idx; + + if (int.TryParse (name.Substring (3), out idx)) + local = cx.Frame.Method.GetLocals ().FirstOrDefault (loc => loc.Index == idx); + } + } else { + local = ctx.CaseSensitive + ? cx.Frame.GetVisibleVariableByName (name) + : FindByName (cx.Frame.GetVisibleVariables (), v => v.Name, name, false); + } + + if (local != null) + return new VariableValueReference (ctx, GetLocalName (cx, local), local); + + return FindByName (OnGetLocalVariables (ctx), v => v.Name, name, ctx.CaseSensitive); + } catch (InvalidStackFrameException) { + return null; + } catch (AbsentInformationException) { + return null; + } + } + + protected override IEnumerable OnGetLocalVariables (EvaluationContext ctx) + { + var cx = (SoftEvaluationContext) ctx; + + if (InGeneratedClosureOrIteratorType (cx)) { + var vthis = GetThisReference (cx); + return GetHoistedLocalVariables (cx, vthis).Union (GetLocalVariables (cx)); + } + + if (IsLocalFunction (cx)) { + var vthis = GetClosureReference (cx); + // if there's no closure reference then it didn't capture anything + if (vthis != null) { + return GetHoistedLocalVariables (cx, vthis).Union (GetLocalVariables (cx)); + } + } + + return GetLocalVariables (cx); + } + + static VariableValueReference GetClosureReference (SoftEvaluationContext cx) + { + foreach (var local in cx.Frame.Method.GetLocals ()) { + if (IsClosureReferenceLocal (local)) { + return new VariableValueReference (cx, local.Name, local); + } + } + return null; + } + + IEnumerable GetLocalVariables (SoftEvaluationContext cx) + { + LocalVariable[] locals; + + try { + locals = cx.Frame.GetVisibleVariables ().Where (x => !x.IsArg && ((IsClosureReferenceLocal (x) && IsGeneratedType (x.Type)) || !IsGeneratedTemporaryLocal (x))).ToArray (); + } catch (InvalidStackFrameException) { + yield break; + } catch (AbsentInformationException) { + yield break; + } + + if (locals.Length == 0) + yield break; + + var batch = new LocalVariableBatch (locals); + + for (int i = 0; i < locals.Length; i++) { + if (IsClosureReferenceLocal (locals[i]) && IsGeneratedType (locals[i].Type)) { + foreach (var gv in GetHoistedLocalVariables (cx, new VariableValueReference (cx, locals[i].Name, locals[i], batch))) { + yield return gv; + } + } else if (!IsGeneratedTemporaryLocal (locals[i])) { + yield return new VariableValueReference (cx, GetLocalName (cx, locals[i]), locals[i], batch); + } + } + } + + public override bool HasMember (EvaluationContext ctx, object type, string memberName, BindingFlags bindingFlags) + { + var tm = (TypeMirror) type; + + while (tm != null) { + var field = FindByName (tm.GetFields (), f => f.Name, memberName, ctx.CaseSensitive); + + if (field != null) + return true; + + var prop = FindByName (tm.GetProperties (), p => p.Name, memberName, ctx.CaseSensitive); + + if (prop != null) { + var getter = prop.GetGetMethod (bindingFlags.HasFlag (BindingFlags.NonPublic)); + if (getter != null) + return true; + } + + if (bindingFlags.HasFlag (BindingFlags.DeclaredOnly)) + break; + + tm = tm.BaseType; + } + + return false; + } + + static bool IsAnonymousType (TypeMirror type) + { + return type.Name.StartsWith ("<>__AnonType", StringComparison.Ordinal); + } + + protected override ValueReference GetMember (EvaluationContext ctx, object t, object co, string name) + { + return OnGetMember (ctx, null, t, co, name); + } + + protected override ValueReference OnGetMember (EvaluationContext ctx, IObjectSource objectSource, object t, object co, string name) + { + var type = t as TypeMirror; + var tupleNames = GetTupleElementNames (objectSource, ctx); + while (type != null) { + var field = FindByName (type.GetFields (), f => MapTupleName(f.Name, tupleNames), name, ctx.CaseSensitive); + + if (field != null && (field.IsStatic || co != null)) + return new FieldValueReference (ctx, field, co, type); + + var prop = FindByName (type.GetProperties (), p => p.Name, name, ctx.CaseSensitive); + + if (prop != null && (IsStatic (prop) || co != null)) { + var getter = prop.GetGetMethod (true); + // Optimization: if the property has a CompilerGenerated backing field, use that instead. + // This way we avoid overhead of invoking methods on the debugee when the value is requested. + //But also check that this method is not virtual, because in that case we need to call getter to invoke override + if (!getter.IsVirtual) { + var cgFieldName = string.Format ("<{0}>{1}", prop.Name, IsAnonymousType (type) ? "" : "k__BackingField"); + if ((field = FindByName (type.GetFields (), f => f.Name, cgFieldName, true)) != null && IsCompilerGenerated (field)) + return new FieldValueReference (ctx, field, co, type, prop.Name, ObjectValueFlags.Property); + } + // Backing field not available, so do things the old fashioned way. + return getter != null ? new PropertyValueReference (ctx, prop, co, type, getter, null) : null; + } + if (type.IsInterface) { + foreach (var inteface in type.GetInterfaces ()) { + var result = GetMember (ctx, inteface, co, name); + if (result != null) + return result; + } + //foreach above recursively checked all "base" interfaces + //nothing was found, quit, otherwise we would loop forever + return null; + } + + type = type.BaseType; + } + + return null; + } + + static string MapTupleName (string name, string [] tupleNames) + { + if (tupleNames != null && + name.Length > 4 && + name.StartsWith ("Item", StringComparison.Ordinal) && + int.TryParse (name.Substring (4), out var tupleIndex) && + tupleNames.Length >= tupleIndex && + tupleNames [tupleIndex - 1] != null) + return tupleNames [tupleIndex - 1]; + + return name; + } + + static bool IsCompilerGenerated (FieldInfoMirror field) + { + var attrs = field.GetCustomAttributes (true); + var generated = GetAttribute (attrs); + + return generated != null; + } + + static bool IsStatic (PropertyInfoMirror prop) + { + var met = prop.GetGetMethod (true) ?? prop.GetSetMethod (true); + + return met.IsStatic; + } + + static T FindByName (IEnumerable items, Func getName, string name, bool caseSensitive) + { + var best = default(T); + + foreach (var item in items) { + var itemName = getName (item); + + if (itemName == name) + return item; + + if (!caseSensitive && itemName.Equals (name, StringComparison.CurrentCultureIgnoreCase)) + best = item; + } + + return best; + } + + protected override IEnumerable GetMembers (EvaluationContext ctx, object t, object co, BindingFlags bindingFlags) + { + return GetMembers (ctx, null, t, co, bindingFlags); + } + + string [] GetTupleElementNames (IObjectSource source, EvaluationContext ctx) + { + switch (source) { + case FieldValueReference field: + if ((field.Type as TypeMirror)?.Name?.StartsWith ("ValueTuple`", StringComparison.Ordinal) != true) + return null; + return field.GetTupleElementNames (); + case PropertyValueReference prop: + if ((prop.Type as TypeMirror)?.Name?.StartsWith ("ValueTuple`", StringComparison.Ordinal) != true) + return null; + return prop.GetTupleElementNames (); + case VariableValueReference variable: + if ((variable.Type as TypeMirror)?.Name?.StartsWith ("ValueTuple`", StringComparison.Ordinal) != true) + return null; + return variable.GetTupleElementNames ((SoftEvaluationContext)ctx); + default: + return null; + } + } + + protected override IEnumerable GetMembers (EvaluationContext ctx, IObjectSource objectSource, object t, object co, BindingFlags bindingFlags) + { + var subProps = new Dictionary (); + var type = t as TypeMirror; + TypeMirror realType = null; + + if (co != null && (bindingFlags & BindingFlags.Instance) != 0) + realType = GetValueType (ctx, co) as TypeMirror; + + // First of all, get a list of properties overriden in sub-types + while (realType != null && realType != type) { + foreach (var prop in realType.GetProperties (bindingFlags | BindingFlags.DeclaredOnly)) { + var met = prop.GetGetMethod (true); + + if (met == null || met.GetParameters ().Length != 0 || met.IsAbstract || !met.IsVirtual || met.IsStatic) + continue; + + if (met.IsPublic && ((bindingFlags & BindingFlags.Public) == 0)) + continue; + + if (!met.IsPublic && ((bindingFlags & BindingFlags.NonPublic) == 0)) + continue; + + subProps [prop.Name] = prop; + } + + realType = realType.BaseType; + } + + var tupleNames = GetTupleElementNames (objectSource, ctx); + bool hasExplicitInterface = false; + while (type != null) { + var fieldsBatch = new FieldReferenceBatch (co); + foreach (var field in type.GetFields ()) { + if (field.IsStatic && ((bindingFlags & BindingFlags.Static) == 0)) + continue; + + if (!field.IsStatic && ((bindingFlags & BindingFlags.Instance) == 0)) + continue; + + if (field.IsPublic && ((bindingFlags & BindingFlags.Public) == 0)) + continue; + + if (!field.IsPublic && ((bindingFlags & BindingFlags.NonPublic) == 0)) + continue; + if (field.IsStatic) { + yield return new FieldValueReference (ctx, field, co, type); + } else { + fieldsBatch.Add (field); + yield return new FieldValueReference (ctx, field, co, type, MapTupleName (field.Name, tupleNames), ObjectValueFlags.Field, fieldsBatch); + } + } + + foreach (var prop in type.GetProperties (bindingFlags)) { + var getter = prop.GetGetMethod (true); + + if (getter == null || getter.GetParameters ().Length != 0 || getter.IsAbstract) + continue; + + if (getter.IsStatic && ((bindingFlags & BindingFlags.Static) == 0)) + continue; + + if (!getter.IsStatic && ((bindingFlags & BindingFlags.Instance) == 0)) + continue; + + if (getter.IsPublic && ((bindingFlags & BindingFlags.Public) == 0)) + continue; + + //This is only possible in case of explicitly implemented interface property, which we handle later + if (getter.IsVirtual && getter.IsPrivate) { + hasExplicitInterface = true; + continue; + } + + if (!getter.IsPublic && ((bindingFlags & BindingFlags.NonPublic) == 0)) + continue; + + // If a property is overriden, return the override instead of the base property + PropertyInfoMirror overridden; + if (getter.IsVirtual && subProps.TryGetValue (prop.Name, out overridden)) { + getter = overridden.GetGetMethod (true); + if (getter == null) + continue; + + yield return new PropertyValueReference (ctx, overridden, co, overridden.DeclaringType, getter, null); + } else { + yield return new PropertyValueReference (ctx, prop, co, type, getter, null); + } + } + + if ((bindingFlags & BindingFlags.DeclaredOnly) != 0) + break; + + type = type.BaseType; + } + + type = t as TypeMirror; + if (type == null || + !hasExplicitInterface || + (bindingFlags & BindingFlags.Instance) == 0 || + (bindingFlags & BindingFlags.Public) == 0) { + yield break; + } + if (Session.VirtualMachine.Version.AtLeast (2, 11)) { + var interfaces = type.GetInterfaces (); + foreach (var intr in interfaces) { + var map = type.GetInterfaceMap (intr); + foreach (var prop in intr.GetProperties (bindingFlags)) { + var getter = prop.GetGetMethod (true); + if (getter == null || getter.GetParameters ().Length != 0) + continue; + var implementationGetter = map.TargetMethods [Array.IndexOf (map.InterfaceMethods, getter)]; + //We are only intersted into private(explicit) implementations because public ones are already handled before + if (implementationGetter.IsPublic) + continue; + yield return new PropertyValueReference (ctx, prop, co, type, getter, null); + } + } + } + } + + static bool IsIEnumerable (TypeMirror type) + { + if (!type.IsInterface) + return false; + + if (type.Namespace == "System.Collections" && type.Name == "IEnumerable") + return true; + + if (type.Namespace == "System.Collections.Generic" && type.Name == "IEnumerable`1") + return true; + + return false; + } + + static ObjectValueFlags GetFlags (MethodMirror method) + { + var flags = ObjectValueFlags.Method; + + if (method.IsStatic) + flags |= ObjectValueFlags.Global; + + if (method.IsPublic) + flags |= ObjectValueFlags.Public; + else if (method.IsPrivate) + flags |= ObjectValueFlags.Private; + else if (method.IsFamily) + flags |= ObjectValueFlags.Protected; + else if (method.IsFamilyAndAssembly) + flags |= ObjectValueFlags.Internal; + else if (method.IsFamilyOrAssembly) + flags |= ObjectValueFlags.InternalProtected; + + return flags; + } + + protected override CompletionData GetMemberCompletionData (EvaluationContext ctx, ValueReference vr) + { + var properties = new HashSet (); + var methods = new HashSet (); + var fields = new HashSet (); + var data = new CompletionData (); + var type = vr.Type as TypeMirror; + bool isEnumerable = false; + + while (type != null) { + if (!isEnumerable && IsIEnumerable (type)) + isEnumerable = true; + + bool isExternal = Session.IsExternalCode (type); + + foreach (var field in type.GetFields ()) { + if (field.IsStatic || field.IsSpecialName || (isExternal && !field.IsPublic) || + IsClosureReferenceField (field) || IsCompilerGenerated (field)) + continue; + + if (fields.Add (field.Name)) + data.Items.Add (new CompletionItem (field.Name, FieldValueReference.GetFlags (field))); + } + + foreach (var property in type.GetProperties ()) { + var getter = property.GetGetMethod (true); + + if (getter == null || getter.IsStatic || (isExternal && !getter.IsPublic)) + continue; + + if (properties.Add (property.Name)) + data.Items.Add (new CompletionItem (property.Name, PropertyValueReference.GetFlags (property, getter))); + } + + foreach (var method in type.GetMethods ()) { + if (method.IsStatic || method.IsConstructor || method.IsSpecialName || (isExternal && !method.IsPublic)) + continue; + + if (methods.Add (method.Name)) + data.Items.Add (new CompletionItem (method.Name, GetFlags (method))); + } + + if (type.BaseType == null && type.FullName != "System.Object") + type = ctx.Adapter.GetType (ctx, "System.Object") as TypeMirror; + else + type = type.BaseType; + } + + type = (TypeMirror) vr.Type; + foreach (var iface in type.GetInterfaces ()) { + if (!isEnumerable && IsIEnumerable (iface)) + isEnumerable = true; + } + + if (isEnumerable) { + // Look for LINQ extension methods... + var linq = ctx.Adapter.GetType (ctx, "System.Linq.Enumerable") as TypeMirror; + if (linq != null) { + foreach (var method in linq.GetMethods ()) { + if (!method.IsStatic || method.IsConstructor || method.IsSpecialName || !method.IsPublic) + continue; + + if (methods.Add (method.Name)) + data.Items.Add (new CompletionItem (method.Name, ObjectValueFlags.Method | ObjectValueFlags.Public)); + } + } + } + + data.ExpressionLength = 0; + + return data; + } + + public override void GetNamespaceContents (EvaluationContext ctx, string namspace, out string[] childNamespaces, out string[] childTypes) + { + var soft = (SoftEvaluationContext) ctx; + var types = new HashSet (); + var namespaces = new HashSet (); + var namspacePrefix = namspace.Length > 0 ? namspace + "." : ""; + + foreach (var type in soft.Session.GetAllTypes ()) { + if (type.Namespace == namspace || type.Namespace.StartsWith (namspacePrefix, StringComparison.InvariantCulture)) { + namespaces.Add (type.Namespace); + types.Add (type.FullName); + } + } + + childNamespaces = new string [namespaces.Count]; + namespaces.CopyTo (childNamespaces); + + childTypes = new string [types.Count]; + types.CopyTo (childTypes); + } + + protected override ObjectValue CreateObjectValueImpl (EvaluationContext ctx, IObjectValueSource source, ObjectPath path, object obj, ObjectValueFlags flags) + { + try { + return base.CreateObjectValueImpl (ctx, source, path, obj, flags); + } + catch (NotSupportedException e) { + throw new EvaluatorException ("Evaluation failed: {0}", e.Message); + } + } + + protected override IEnumerable OnGetParameters (EvaluationContext ctx) + { + var soft = (SoftEvaluationContext) ctx; + LocalVariable[] locals; + + try { + locals = soft.Frame.Method.GetLocals ().Where (x => x.IsArg && !IsClosureReferenceLocal (x)).ToArray (); + } catch (InvalidStackFrameException) { + yield break; + } catch (AbsentInformationException) { + yield break; + } + + if (locals.Length == 0) + yield break; + + var batch = new LocalVariableBatch (locals); + + for (int i = 0; i < locals.Length; i++) { + var name = !string.IsNullOrEmpty (locals[i].Name) ? locals[i].Name : "arg" + locals[i].Index; + yield return new VariableValueReference (ctx, name, locals[i], batch); + } + } + + protected override ValueReference OnGetThisReference (EvaluationContext ctx) + { + var cx = (SoftEvaluationContext) ctx; + + if (InGeneratedClosureOrIteratorType (cx)) + return GetHoistedThisReference (cx); + + return GetThisReference (cx); + } + + static ValueReference GetThisReference (SoftEvaluationContext ctx) + { + return ctx.Frame.Method.IsStatic ? null : new ThisValueReference (ctx, ctx.Frame); + } + + public override ValueReference GetCurrentException (EvaluationContext ctx) + { + try { + var cx = (SoftEvaluationContext) ctx; + var exc = cx.Session.GetExceptionObject (cx.Thread); + + return exc != null ? LiteralValueReference.CreateTargetObjectLiteral (ctx, ctx.Options.CurrentExceptionTag, exc) : null; + } catch (AbsentInformationException) { + return null; + } + } + + public override bool IsGenericType (EvaluationContext ctx, object type) + { + return type != null && ((TypeMirror) type).IsGenericType; + } + + public override IEnumerable GetGenericTypeArguments (EvaluationContext ctx, object type) + { + return ((TypeMirror)type).GetGenericArguments (); + } + + public override object[] GetTypeArgs (EvaluationContext ctx, object type) + { + var tm = (TypeMirror) type; + + if (tm.VirtualMachine.Version.AtLeast (2, 15)) + return tm.GetGenericArguments (); + + // fall back to parsing them from the from the FullName + var names = new List (); + var typeName = tm.FullName; + int i = typeName.IndexOf ('`'); + + if (i != -1) { + i = typeName.IndexOf ('[', i); + if (i == -1) + return new object [0]; + int si = ++i; + int nt = 0; + for (; i < typeName.Length && (nt > 0 || typeName[i] != ']'); i++) { + if (typeName[i] == '[') + nt++; + else if (typeName[i] == ']') + nt--; + else if (typeName[i] == ',' && nt == 0) { + names.Add (typeName.Substring (si, i - si)); + si = i + 1; + } + } + names.Add (typeName.Substring (si, i - si)); + var types = new object [names.Count]; + for (int n=0; n 0) { + string args = ""; + + foreach (var argType in typeArgs) { + if (args.Length > 0) + args += ","; + + string tn; + + if (!(argType is TypeMirror atm)) { + var att = (Type) argType; + + tn = att.FullName + ", " + att.Assembly.GetName (); + } else { + tn = atm.FullName + ", " + atm.Assembly.GetName (); + } + + if (tn.IndexOf (',') != -1) + tn = "[" + tn + "]"; + + args += tn; + } + + name += "[" +args + "]"; + } + + var tm = cx.Session.GetType (name); + if (tm != null) + return tm; + + foreach (var asm in cx.Domain.GetAssemblies ()) { + tm = asm.GetType (name, false, false); + if (tm != null) + return tm; + } + + var method = cx.Frame.Method; + if (method.IsGenericMethod && !method.IsGenericMethodDefinition) { + var definition = method.GetGenericMethodDefinition (); + var names = definition.GetGenericArguments (); + var types = method.GetGenericArguments (); + + for (i = 0; i < names.Length; i++) { + if (names[i].FullName == name) + return types[i]; + } + } + + var declaringType = method.DeclaringType; + if (declaringType.IsGenericType && !declaringType.IsGenericTypeDefinition) { + var definition = declaringType.GetGenericTypeDefinition (); + var types = declaringType.GetGenericArguments (); + var names = definition.GetGenericArguments (); + + for (i = 0; i < names.Length; i++) { + if (names[i].FullName == name) + return types[i]; + } + } + + return null; + } + + protected override object GetBaseTypeWithAttribute (EvaluationContext ctx, object type, object attrType) + { + var atm = attrType as TypeMirror; + var tm = type as TypeMirror; + + while (tm != null && atm != null) { + if (tm.GetCustomAttributes (atm, false).Any ()) + return tm; + + tm = tm.BaseType; + } + + return null; + } + + public override object GetParentType (EvaluationContext ctx, object type) + { + if (type is TypeMirror tm) { + int plus = tm.FullName.LastIndexOf ('+'); + + return plus != -1 ? GetType (ctx, tm.FullName.Substring (0, plus)) : null; + } + + return ((Type) type).DeclaringType; + } + + public override IEnumerable GetNestedTypes (EvaluationContext ctx, object type) + { + var tm = (TypeMirror) type; + + foreach (var nested in tm.GetNestedTypes ()) + if (!IsGeneratedType (nested)) + yield return nested; + } + + public override IEnumerable GetImplementedInterfaces (EvaluationContext ctx, object type) + { + var tm = (TypeMirror) type; + + foreach (var nested in tm.GetInterfaces ()) + yield return nested; + } + + public override string GetTypeName (EvaluationContext ctx, object type) + { + var tm = type as TypeMirror ?? (type as ArgumentType)?.Type; + + if (tm != null) { + if (IsGeneratedType (tm)) { + // Return the name of the container-type. + return tm.FullName.Substring (0, tm.FullName.LastIndexOf ('+')); + } + + return tm.FullName; + } + + return ((Type) type).FullName; + } + + public override object GetValueType (EvaluationContext ctx, object val) + { + if (val == null) + return typeof (object); + if (val is ArrayMirror) + return ((ArrayMirror) val).Type; + if (val is ObjectMirror) + return ((ObjectMirror) val).Type; + if (val is EnumMirror) + return ((EnumMirror) val).Type; + if (val is StructMirror) + return ((StructMirror) val).Type; + if (val is PointerValue) + return ((PointerValue) val).Type; + if (val is DelayedLambdaValue) + return ((DelayedLambdaValue)val).DelayedType; + if (val is PrimitiveValue) { + var pv = (PrimitiveValue) val; + if (pv.Value == null) + return typeof (object); + + return pv.Value.GetType (); + } + + throw new NotSupportedException (val.GetType ().FullName); + } + + public override object GetBaseType (EvaluationContext ctx, object type) + { + return type is TypeMirror tm ? tm.BaseType : null; + } + + public override bool HasMethodWithParamLength (EvaluationContext ctx, object targetType, string methodName, BindingFlags flags, int paramLength) + { + var soft = (SoftEvaluationContext)ctx; + var currentType = ToTypeMirror (ctx, targetType); + var allowInstance = (flags & BindingFlags.Instance) != 0; + var allowStatic = (flags & BindingFlags.Static) != 0; + var onlyPublic = (flags & BindingFlags.Public) != 0; + + while (currentType != null) { + var methods = GetMethodsByName (soft, currentType, methodName); + + foreach (var method in methods) { + var parms = method.GetParameters (); + if (parms.Length == paramLength && ((method.IsStatic && allowStatic) || (!method.IsStatic && allowInstance))) { + if (onlyPublic && !method.IsPublic) + continue; + return true; + } + } + + if (methodName == ".ctor") + break; + + if (currentType.BaseType == null && currentType.FullName != "System.Object") + currentType = soft.Adapter.GetType (soft, "System.Object") as TypeMirror; + else + currentType = currentType.BaseType; + } + + return false; + } + + public override bool HasMethod (EvaluationContext ctx, object targetType, string methodName, object [] genericTypeArgs, object [] argTypes, BindingFlags flags) + { + return HasMethod (ctx, targetType, methodName, genericTypeArgs, argTypes, flags, out _); + } + + public override bool HasMethod (EvaluationContext ctx, object targetType, string methodName, object[] genericTypeArgs, object[] argTypes, BindingFlags flags, out Tuple[] resolvedLambdaTypes) + { + var soft = (SoftEvaluationContext) ctx; + var tm = ToTypeMirror (ctx, targetType); + ArgumentType[] typeArgs = null; + ArgumentType [] types = null; + var hasDelayed = false; + resolvedLambdaTypes = null; + + if (genericTypeArgs != null) { + typeArgs = new ArgumentType [genericTypeArgs.Length]; + for (int n = 0; n < genericTypeArgs.Length; n++) + typeArgs[n] = ToArgumentType (ctx, genericTypeArgs[n]); + } + + if (argTypes != null) { + types = new ArgumentType [argTypes.Length]; + for (int n = 0; n < argTypes.Length; n++) { + var argType = ToArgumentType (ctx, argTypes[n]); + types[n] = argType; + hasDelayed |= argType.IsDelayed; + } + } + + var allowInstance = (flags & BindingFlags.Instance) != 0; + var allowStatic = (flags & BindingFlags.Static) != 0; + var throwIfNotFound = false; + var tryCasting = true; + MethodMirror method; + + if (hasDelayed) { + var candidates = OverloadResolveMulti (soft, tm, methodName, typeArgs, null, types, allowInstance, allowStatic, throwIfNotFound, tryCasting); + method = null; + } else { + method = OverloadResolve (soft, tm, methodName, typeArgs, types, allowInstance, allowStatic, throwIfNotFound, tryCasting); + } + + return method != null; + } + + // Returns the best method from `candidates' using lambda types in + // `argTypes'. Non lambda types in argtypes will be ignored. We make sure + // lamdas have its exected type by compiling them. If a matched method is + // found, return an array of tuples of (argument index, resolved argument + // type for lambda) through 'resolved'. + + public override bool IsExternalType (EvaluationContext ctx, object type) + { + var tm = type as TypeMirror; + + return tm == null || ((SoftEvaluationContext)ctx).Session.IsExternalCode (tm); + } + + public override bool IsString (EvaluationContext ctx, object val) + { + return val is StringMirror; + } + + public override bool IsArray (EvaluationContext ctx, object val) + { + return val is ArrayMirror; + } + + public override bool IsValueType (object type) + { + return type is TypeMirror tm && tm.IsValueType; + } + + public override bool IsPrimitiveType (object type) + { + if (!(type is TypeMirror tm)) + return false; + + if (tm.IsPrimitive) + return true; + + if (tm.IsValueType && tm.Namespace == "System" && (tm.Name == "nfloat" || tm.Name == "nint")) + return true; + + return false; + } + + public override bool IsClass (EvaluationContext ctx, object type) + { + return type is TypeMirror tm && (tm.IsClass || tm.IsValueType) && !tm.IsPrimitive; + } + + public override bool IsNull (EvaluationContext ctx, object val) + { + return val == null || ((val is PrimitiveValue) && ((PrimitiveValue)val).Value == null) || ((val is PointerValue) && ((PointerValue)val).Address == 0); + } + + public override bool IsPrimitive (EvaluationContext ctx, object val) + { + if (val is PrimitiveValue || val is StringMirror || val is PointerValue) + return true; + if (!(val is StructMirror sm)) + return false; + if (sm.Type.IsPrimitive) + return true; + if (sm.Type.Namespace == "System" && (sm.Type.Name == "nfloat" || sm.Type.Name == "nint")) + return true; + return false; + } + + public override bool IsPointer (EvaluationContext ctx, object val) + { + return val is PointerValue; + } + + public override bool IsEnum (EvaluationContext ctx, object val) + { + return val is EnumMirror; + } + + public override bool IsDelayedType (EvaluationContext ctx, object type) + { + return type is DelayedLambdaType; + } + + public override bool IsPublic (EvaluationContext ctx, object type) + { + var tm = ToTypeMirror (ctx, type); + + return tm != null && tm.IsPublic; + } + + protected override TypeDisplayData OnGetTypeDisplayData (EvaluationContext ctx, object type) + { + Dictionary memberData = null; + var soft = (SoftEvaluationContext) ctx; + bool isCompilerGenerated = false; + string displayValue = null; + string displayName = null; + string displayType = null; + string proxyType = null; + + try { + var tm = (TypeMirror) type; + + foreach (var attr in tm.GetCustomAttributes (true)) { + var attrName = attr.Constructor.DeclaringType.FullName; + switch (attrName) { + case "System.Diagnostics.DebuggerDisplayAttribute": + var display = BuildAttribute (attr); + displayValue = display.Value; + displayName = display.Name; + displayType = display.Type; + break; + case "System.Diagnostics.DebuggerTypeProxyAttribute": + var proxy = BuildAttribute (attr); + proxyType = proxy.ProxyTypeName; + if (!string.IsNullOrEmpty (proxyType)) + ForceLoadType (soft, proxyType); + break; + case "System.Runtime.CompilerServices.CompilerGeneratedAttribute": + isCompilerGenerated = true; + break; + } + } + + foreach (var field in tm.GetFields ()) { + var attrs = field.GetCustomAttributes (true); + var browsable = GetAttribute (attrs); + + if (browsable == null) { + var generated = GetAttribute (attrs); + if (generated != null) + browsable = new DebuggerBrowsableAttribute (DebuggerBrowsableState.Never); + } + + if (browsable != null) { + if (memberData == null) + memberData = new Dictionary (); + memberData [field.Name] = browsable.State; + } + } + + foreach (var property in tm.GetProperties ()) { + var browsable = GetAttribute (property.GetCustomAttributes (true)); + if (browsable != null) { + if (memberData == null) + memberData = new Dictionary (); + memberData [property.Name] = browsable.State; + } + } + } catch (Exception ex) { + soft.Session.WriteDebuggerOutput (true, ex.ToString ()); + } + + return new TypeDisplayData (proxyType, displayValue, displayType, displayName, isCompilerGenerated, memberData); + } + + static T GetAttribute (CustomAttributeDataMirror[] attrs) + { + foreach (var attr in attrs) { + if (attr.Constructor.DeclaringType.FullName == typeof (T).FullName) + return BuildAttribute (attr); + } + + return default (T); + } + + public override bool IsTypeLoaded (EvaluationContext ctx, string typeName) + { + var soft = (SoftEvaluationContext) ctx; + + return soft.Session.GetType (typeName) != null; + } + + public override bool IsTypeLoaded (EvaluationContext ctx, object type) + { + var tm = (TypeMirror) type; + + if (tm.VirtualMachine.Version.AtLeast (2, 23)) + return tm.IsInitialized; + + return IsTypeLoaded (ctx, tm.FullName); + } + + public override bool ForceLoadType (EvaluationContext ctx, object type) + { + var soft = (SoftEvaluationContext) ctx; + var tm = (TypeMirror) type; + + if (!tm.VirtualMachine.Version.AtLeast (2, 23)) + return IsTypeLoaded (ctx, tm.FullName); + + if (tm.IsInitialized) + return true; + + if (!tm.Attributes.HasFlag (TypeAttributes.BeforeFieldInit)) + return false; + + var cctor = OverloadResolve (soft, tm, ".cctor", null, new ArgumentType[0], false, true, false); + if (cctor == null) + return true; + + try { + lock (cctor.VirtualMachine) { + tm.InvokeMethod(soft.Thread, cctor, new Value [0], InvokeOptions.DisableBreakpoints | InvokeOptions.SingleThreaded); + } + } catch { + return false; + } finally { + soft.Session.StackVersion++; + } + + return true; + } + + static T BuildAttribute (CustomAttributeDataMirror attr) + { + var args = new List (); + var type = typeof (T); + + foreach (var arg in attr.ConstructorArguments) { + object val = arg.Value; + + if (val is TypeMirror) { + // The debugger attributes that take a type as parameter of the constructor have + // a corresponding constructor overload that takes a type name. We'll use that + // constructor because we can't load target types in the debugger process. + // So what we do here is convert the Type to a String. + var tm = (TypeMirror) val; + // Workaround for older Mono runtime that doesn't support generics, simply use ICollection viewer instead generic version + if (!tm.VirtualMachine.Version.AtLeast (2, 15) && tm.FullName.StartsWith ("System.Collections.Generic.CollectionDebuggerView`", StringComparison.Ordinal)) + val = "System.Collections.CollectionDebuggerView, " + tm.Assembly.ManifestModule.Name; + else + val = tm.FullName + ", " + tm.Assembly.ManifestModule.Name; + } else if (val is EnumMirror) { + var em = (EnumMirror) val; + + if (type == typeof (DebuggerBrowsableAttribute)) + val = (DebuggerBrowsableState) em.Value; + else + val = em.Value; + } + + args.Add (val); + } + + var attribute = Activator.CreateInstance (type, args.ToArray ()); + + foreach (var arg in attr.NamedArguments) { + object val = arg.TypedValue.Value; + string postFix = ""; + + if (arg.TypedValue.ArgumentType == typeof (Type)) + postFix = "TypeName"; + if (arg.Field != null) + type.GetField (arg.Field.Name + postFix).SetValue (attribute, val); + else if (arg.Property != null) + type.GetProperty (arg.Property.Name + postFix).SetValue (attribute, val, null); + } + + return (T) attribute; + } + + public class ArgumentType + { + TypeMirror type; + DelayedLambdaType delayedType; + public bool RepresentsNull { get; internal set; } + public bool IsDelayed { get; internal set; } + public TypeMirror Type { + get { + if (IsDelayed) + throw new NotSupportedException(); + return type; + } + internal set { + type = value; + } + } + public DelayedLambdaType DelayedType { + get { + if (!IsDelayed) + throw new NotSupportedException(); + return delayedType; + } + internal set { + delayedType = value; + } + } + + public static implicit operator TypeMirror (ArgumentType d) + { + return d.Type; + } + + public static implicit operator ArgumentType (TypeMirror d) + { + return new ArgumentType () { Type = d }; + } + } + + ArgumentType ToArgumentType (EvaluationContext ctx, object type) + { + var tm = type as TypeMirror; + var isDelayed = ctx.Adapter.IsDelayedType (ctx, type); + + if (isDelayed) { + return new ArgumentType { + IsDelayed = true, + DelayedType = type as DelayedLambdaType + }; + } + + return new ArgumentType { + Type = tm ?? (TypeMirror)GetType (ctx, ((Type)type).FullName), + // GetValueType method always returns `typeof (object)` when value is `null` + // We need to store this information because later in process this information is lost + // when needed by OverloadResolve when trying to determine correct method overload + RepresentsNull = (type as Type) == typeof (object) + }; + } + + TypeMirror ToTypeMirror (EvaluationContext ctx, object type) + { + var tm = type as TypeMirror; + + return tm ?? (TypeMirror) GetType (ctx, ((Type) type).FullName); + } + + public override object RuntimeInvoke (EvaluationContext ctx, object targetType, object target, string methodName, object[] genericTypeArgs, object[] argTypes, object[] argValues) + { + object[] outArgs; + return RuntimeInvoke (ctx, targetType, target, methodName, genericTypeArgs, argTypes, argValues, false, out outArgs); + } + + public override object RuntimeInvoke (EvaluationContext ctx, object targetType, object target, string methodName, object[] genericTypeArgs, object[] argTypes, object[] argValues, out object[] outArgs) + { + return RuntimeInvoke (ctx, targetType, target, methodName, genericTypeArgs, argTypes, argValues, true, out outArgs); + } + + private object RuntimeInvoke (EvaluationContext ctx, object targetType, object target, string methodName, object[] genericTypeArgs, object[] argTypes, object[] argValues,bool enableOutArgs, out object[] outArgs) + { + var type = ToTypeMirror (ctx, targetType); + var soft = (SoftEvaluationContext) ctx; + + soft.AssertTargetInvokeAllowed (); + + var genericTypes = new ArgumentType [genericTypeArgs != null ? genericTypeArgs.Length : 0]; + for (int n = 0; n < genericTypes.Length; n++) + genericTypes[n] = ToArgumentType (soft, genericTypeArgs[n]); + + var types = new ArgumentType [argTypes.Length]; + for (int n = 0; n < argTypes.Length; n++) + types[n] = ToArgumentType (soft, argTypes[n]); + + var method = OverloadResolve (soft, type, methodName, genericTypes, types, target != null, target == null, true); + var mparams = method.GetParameters (); + var values = new Value [argValues.Length]; + + for (int n = 0; n < argValues.Length; n++) { + var param_type = mparams [n].ParameterType; + if (param_type.FullName != types [n].Type.FullName && !param_type.IsAssignableFrom (types [n].Type) && param_type.IsGenericType && + !(param_type.IsGenericType && param_type.FullName.StartsWith ("System.Nullable`1", StringComparison.Ordinal))) { + var throwCastException = true; + + if (method.VirtualMachine.Version.AtLeast (2, 15)) { + var args = param_type.GetGenericArguments (); + + if (args.Length == genericTypes.Length) { + var real_type = soft.Adapter.GetType (soft, param_type.GetGenericTypeDefinition ().FullName, genericTypes); + + values [n] = (Value)TryCast (soft, (Value)argValues [n], real_type); + if (!(values [n] == null && argValues [n] != null && !soft.Adapter.IsNull (soft, argValues [n]))) + throwCastException = false; + } + } + + if (throwCastException) { + var fromType = !IsGeneratedType (types [n].Type) ? soft.Adapter.GetDisplayTypeName (soft, types [n]) : types [n].Type.FullName; + var toType = soft.Adapter.GetDisplayTypeName (soft, param_type); + + throw new EvaluatorException ("Argument {0}: Cannot implicitly convert `{1}' to `{2}'", n, fromType, toType); + } + } else if (argValues[n] is DelayedLambdaValue) { + outArgs = null; + return null; + /*string error; + var val = CompileAndLoadLambdaValue (soft, (DelayedLambdaValue) argValues[n], param_type, out error); + values[n] = (Value) val; + + if (error != null) + throw new EvaluatorException ("Invalid arguments for method `{0}': Argument {1}: {2}", methodName, n, error);*/ + } else if (param_type.FullName != types [n].Type.FullName && !param_type.IsAssignableFrom (types [n].Type) && !types[n].RepresentsNull && CanForceCast (ctx, types [n], param_type)) { + values [n] = (Value)TryCast (ctx, argValues [n], param_type); + } else { + values [n] = (Value)argValues [n]; + } + } + if (enableOutArgs) { + Value[] outArgsValue; + var result = soft.RuntimeInvoke (method, target ?? targetType, values, out outArgsValue); + outArgs = outArgsValue; + return result; + } else { + outArgs = null; + return soft.RuntimeInvoke (method, target ?? targetType, values); + } + } + + static ArgumentType[] ResolveGenericTypeArguments (MethodMirror method, ArgumentType[] argTypes) + { + var genericArgs = method.GetGenericArguments (); + var types = new ArgumentType[genericArgs.Length]; + var parameters = method.GetParameters (); + var names = new List (); + + // map the generic argument type names + foreach (var arg in genericArgs) + names.Add (arg.Name); + + // map parameter types to generic argument types... + for (int i = 0; i < argTypes.Length && i < parameters.Length; i++) { + if (argTypes[i].IsDelayed) + continue; + + var paramType = parameters[i].ParameterType; + var isArray = paramType.IsArray; + if (isArray) + paramType = paramType.GetElementType (); + + int index = names.IndexOf (paramType.Name); + + if (index != -1 && types[index] == null) { + var argType = argTypes[i].Type; + if (isArray && argType.IsArray) + argType = argType.GetElementType (); + + types[index] = argType; + } + } + + // make sure we have all the generic argument types... + for (int i = 0; i < types.Length; i++) { + if (types[i] == null) + return null; + } + + return types; + } + + static MethodMirror PickFirstCandidate (MethodMirror [] methods) + { + if (methods == null || methods.Length == 0) + return null; + + if (methods.Length == 1) + return methods [0]; + + // If there is an ambiguous match, just pick the first match. If the user was expecting + // something else, he can provide more specific arguments + + //if (!throwIfNotFound) + // return null; + //if (methodName != null) + // throw new EvaluatorException ("Ambiguous method `{0}'; need to use full name", methodName); + //else + // throw new EvaluatorException ("Ambiguous arguments for indexer.", methodName); + + return methods [0]; + } + + public static MethodMirror OverloadResolve (SoftEvaluationContext ctx, TypeMirror type, string methodName, ArgumentType[] genericTypeArgs, ArgumentType[] argTypes, bool allowInstance, bool allowStatic, bool throwIfNotFound, bool tryCasting = true) + { + return OverloadResolve (ctx, type, methodName, genericTypeArgs, null, argTypes, allowInstance, allowStatic, throwIfNotFound, tryCasting); + } + + public static MethodMirror OverloadResolve (SoftEvaluationContext ctx, TypeMirror type, string methodName, ArgumentType [] genericTypeArgs, TypeMirror returnType, ArgumentType [] argTypes, bool allowInstance, bool allowStatic, bool throwIfNotFound, bool tryCasting) + { + var results = OverloadResolveMulti (ctx, type, methodName, genericTypeArgs, returnType, argTypes, allowInstance, allowStatic, throwIfNotFound, tryCasting: tryCasting); + return PickFirstCandidate (results); + } + + public static MethodMirror[] GetMethodsByName (SoftEvaluationContext ctx, TypeMirror type, string methodName) + { + const BindingFlags flag = BindingFlags.DeclaredOnly | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static; + MethodMirror[] methods = null; + var cache = ctx.Session.OverloadResolveCache; + + if (ctx.CaseSensitive) { + lock (cache) { + cache.TryGetValue (Tuple.Create (type, methodName), out methods); + } + } + + if (methods == null) { + if (type.VirtualMachine.Version.AtLeast (2, 7)) { + methods = type.GetMethodsByNameFlags (methodName, flag, !ctx.CaseSensitive); + } else { + methods = type.GetMethods (); + } + + if (ctx.CaseSensitive) { + lock (cache) { + cache [Tuple.Create (type, methodName)] = methods; + } + } + } + return methods; + } + + + public static MethodMirror[] OverloadResolveMulti (SoftEvaluationContext ctx, TypeMirror type, string methodName, ArgumentType [] genericTypeArgs, TypeMirror returnType, ArgumentType [] argTypes, bool allowInstance, bool allowStatic, bool throwIfNotFound, bool tryCasting) + { + var candidates = new List (); + var currentType = type; + + while (currentType != null) { + MethodMirror [] methods = GetMethodsByName(ctx, currentType, methodName); + + foreach (var method in methods) { + if (method.Name == methodName || (!ctx.CaseSensitive && method.Name.Equals (methodName, StringComparison.CurrentCultureIgnoreCase))) { + MethodMirror actualMethod; + + if (argTypes != null && method.VirtualMachine.Version.AtLeast (2, 24) && method.IsGenericMethod) { + var generic = method.GetGenericMethodDefinition (); + ArgumentType [] typeArgs; + + //Console.WriteLine ("Attempting to resolve generic type args for: {0}", GetPrettyMethodName (ctx, generic)); + + if ((genericTypeArgs == null || genericTypeArgs.Length == 0)) + typeArgs = ResolveGenericTypeArguments (generic, argTypes); + else + typeArgs = genericTypeArgs; + + if (typeArgs == null || typeArgs.Length != generic.GetGenericArguments ().Length) { + //Console.WriteLine ("Failed to resolve generic method argument types..."); + continue; + } + + actualMethod = generic.MakeGenericMethod (typeArgs.Select (t => t.Type).ToArray ()); + //Console.WriteLine ("Resolve generic method to: {0}", GetPrettyMethodName (ctx, actualMethod)); + } else { + actualMethod = method; + } + + var parms = actualMethod.GetParameters (); + if (argTypes == null || parms.Length == argTypes.Length && ((actualMethod.IsStatic && allowStatic) || (!actualMethod.IsStatic && allowInstance))) + candidates.Add (actualMethod); + } + } + + if (argTypes == null && candidates.Count > 0) + break; // when argTypes is null, we are just looking for *any* match (not a specific match) + + if (methodName == ".ctor") + break; // Can't create objects using constructor from base classes + + // Make sure that we always pull in at least System.Object methods (this is mostly needed for cases where 'type' was an interface) + if (currentType.BaseType == null && currentType.FullName != "System.Object") + currentType = ctx.Adapter.GetType (ctx, "System.Object") as TypeMirror; + else + currentType = currentType.BaseType; + } + + return OverloadResolveMulti (ctx, type, methodName, genericTypeArgs, returnType, argTypes, candidates, throwIfNotFound, tryCasting); + } + + static bool IsApplicable (SoftEvaluationContext ctx, MethodMirror method, ArgumentType[] genericTypeArgs, TypeMirror returnType, ArgumentType [] types, out string error, out int matchCount, bool tryCasting = true) + { + var mparams = method.GetParameters (); + matchCount = 0; + + for (int i = 0; i < types.Length; i++) { + var param_type = mparams[i].ParameterType; + + if (types [i].RepresentsNull && !SoftEvaluationContext.IsValueTypeOrPrimitive (param_type)) + continue; + + if (types[i].IsDelayed) { + var lambdaType = types[i].DelayedType; + if (lambdaType.IsAcceptableType (param_type)) { + matchCount++; + } + continue; + } + + if (param_type.FullName == types[i].Type.FullName) { + matchCount++; + continue; + } + + if (param_type.IsAssignableFrom (types[i].Type)) + continue; + + if (param_type.IsGenericType) { + if (genericTypeArgs != null && method.VirtualMachine.Version.AtLeast (2, 12)) { + // FIXME: how can we make this more definitive? + if (param_type.GetGenericArguments ().Length == genericTypeArgs.Length) + continue; + } else { + // no way to check... assume it'll work? + continue; + } + } + + if (tryCasting && CanCast (ctx, types [i], param_type)) + continue; + + if (CanDoPrimaryCast(types[i].Type, param_type)) + continue; + + var fromType = !IsGeneratedType (types[i].Type) ? ctx.Adapter.GetDisplayTypeName (ctx, types[i]) : types[i].Type.FullName; + var toType = ctx.Adapter.GetDisplayTypeName (ctx, param_type); + + error = string.Format ("Argument {0}: Cannot implicitly convert `{1}' to `{2}'", i, fromType, toType); + + return false; + } + + if (returnType != null && returnType != method.ReturnType) { + var actual = ctx.Adapter.GetDisplayTypeName (ctx, method.ReturnType); + var expected = ctx.Adapter.GetDisplayTypeName (ctx, returnType); + + error = string.Format ("Return types do not match: `{0}' vs `{1}'", expected, actual); + + return false; + } + + error = null; + + return true; + } + + static bool CanDoPrimaryCast (TypeMirror fromType, TypeMirror toType) + { + var name = toType.CSharpName; + switch (fromType.CSharpName) { + case "sbyte": return name == "short" || name == "int" || name == "long" || name == "float" || name == "double" || name == "decimal"; + case "byte": return name == "short" || name == "ushort" || name == "int" || name == "uint" || name == "long" || name == "ulong" || name == "float" || name == "double" || name == "decimal"; + case "short": return name == "int" || name == "long" || name == "float" || name == "double" || name == "decimal"; + case "ushort": return name == "int" || name == "uint" || name == "long" || name == "ulong" || name == "float" || name == "double" || name == "decimal"; + case "int": return name == "long" || name == "float" || name == "double" || name == "decimal"; + case "uint": return name == "long" || name == "ulong" || name == "float" || name == "double" || name == "decimal"; + case "long": return name == "float" || name == "double" || name == "decimal"; + case "char": return name == "ushort" || name == "int" || name == "uint" || name == "long" || name == "ulong" || name == "float" || name == "double" || name == "decimal"; + case "float": return name == "double"; + case "ulong": return name == "float" || name == "double" || name == "decimal"; + } + return false; + } + + static MethodMirror OverloadResolve (SoftEvaluationContext ctx, TypeMirror type, string methodName, ArgumentType[] genericTypeArgs, TypeMirror returnType, ArgumentType[] argTypes, List candidates, bool throwIfNotFound, bool tryCasting = true) + { + var results = OverloadResolveMulti (ctx, type, methodName, genericTypeArgs, returnType, argTypes, candidates, throwIfNotFound, tryCasting: tryCasting); + return PickFirstCandidate (results); + } + + static MethodMirror[] OverloadResolveMulti (SoftEvaluationContext ctx, TypeMirror type, string methodName, ArgumentType[] genericTypeArgs, TypeMirror returnType, ArgumentType[] argTypes, List candidates, bool throwIfNotFound, bool tryCasting = true) + { + if (candidates.Count == 0) { + if (throwIfNotFound) { + var typeName = ctx.Adapter.GetDisplayTypeName (ctx, type); + + if (methodName == null) + throw new EvaluatorException ("Indexer not found in type `{0}'.", typeName); + + if (genericTypeArgs != null && genericTypeArgs.Length > 0) { + var types = string.Join (", ", genericTypeArgs.Select (t => ctx.Adapter.GetDisplayTypeName (ctx, t))); + + throw new EvaluatorException ("Method `{0}<{1}>' not found in type `{2}'.", methodName, types, typeName); + } + + throw new EvaluatorException ("Method `{0}' not found in type `{1}'.", methodName, typeName); + } + + return null; + } + + if (argTypes == null) { + // This is just a probe to see if the type contains *any* methods of the given name + return new MethodMirror[] { candidates [0] }; + } + + if (candidates.Count == 1) { + if (IsApplicable (ctx, candidates [0], genericTypeArgs, returnType, argTypes, out var error, out var matchCount, tryCasting)) + return new MethodMirror [] { candidates [0] }; + + if (throwIfNotFound) + throw new EvaluatorException ("Invalid arguments for method `{0}': {1}", methodName, error); + + return null; + } + + // Ok, now we need to find exact matches. + var bestCandidates = new List(); + int bestCount = -1; + + foreach (var method in candidates) { + string error; + int matchCount; + + if (!IsApplicable (ctx, method, genericTypeArgs, returnType, argTypes, out error, out matchCount, tryCasting)) + continue; + + if (matchCount == bestCount) { + bestCandidates.Add (method); + } else if (matchCount > bestCount) { + bestCandidates = new List { method }; + bestCount = matchCount; + } + } + + if (bestCandidates.Count == 0) { + if (!throwIfNotFound) + return null; + + if (methodName != null) + throw new EvaluatorException ("Invalid arguments for method `{0}'.", methodName); + + throw new EvaluatorException ("Invalid arguments for indexer."); + } + return bestCandidates.ToArray (); + } + + public override object TargetObjectToObject (EvaluationContext ctx, object obj) + { + if (obj is StringMirror) { + return MirrorStringToString (ctx, (StringMirror)obj); + } + + if (obj is PrimitiveValue) + return ((PrimitiveValue)obj).Value; + + if (obj is PointerValue) + return new EvaluationResult ("0x" + ((PointerValue)obj).Address.ToString ("x")); + + if (obj is StructMirror) { + var sm = (StructMirror) obj; + + if (sm.Type.IsPrimitive) { + // Boxed primitive + if (sm.Type.FullName == "System.IntPtr") + return new EvaluationResult ("0x" + ((long)((PointerValue)sm.Fields [0]).Address).ToString ("x")); + if (sm.Fields.Length > 0 && (sm.Fields[0] is PrimitiveValue)) + return ((PrimitiveValue)sm.Fields[0]).Value; + } else if (sm.Type.FullName == "System.Decimal") { + var soft = (SoftEvaluationContext) ctx; + + var method = OverloadResolve (soft, sm.Type, "GetBits", null, new [] { new ArgumentType { Type = sm.Type, RepresentsNull = false } }, false, true, false); + + if (method != null) { + ArrayMirror array; + + try { + lock (method.VirtualMachine) { + array = sm.Type.InvokeMethod(soft.Thread, method, new [] { sm }, InvokeOptions.DisableBreakpoints | InvokeOptions.SingleThreaded) as ArrayMirror; + } + } catch { + array = null; + } finally { + soft.Session.StackVersion++; + } + + if (array != null) { + var bits = new int[4]; + + for (int i = 0; i < 4; i++) + bits[i] = (int) TargetObjectToObject (ctx, array[i]); + + return new decimal (bits); + } + } + } + } + + return base.TargetObjectToObject (ctx, obj); + } + + static string MirrorStringToString (EvaluationContext ctx, StringMirror mirror) + { + string str; + + if (ctx.Options.EllipsizeStrings) { + if (mirror.VirtualMachine.Version.AtLeast (2, 10)) { + int length = mirror.Length; + + if (length > ctx.Options.EllipsizedLength) + str = new string (mirror.GetChars (0, ctx.Options.EllipsizedLength)) + EvaluationOptions.Ellipsis; + else + str = mirror.Value; + } else { + str = mirror.Value; + if (str.Length > ctx.Options.EllipsizedLength) + str = str.Substring (0, ctx.Options.EllipsizedLength) + EvaluationOptions.Ellipsis; + } + } else { + str = mirror.Value; + } + + return str; + } + } + + class MethodCall: AsyncOperation + { + readonly InvokeOptions options = InvokeOptions.DisableBreakpoints; + + readonly ManualResetEvent shutdownEvent = new ManualResetEvent (false); + readonly MethodMirror function; + readonly Value[] args; + readonly object obj; + IAsyncResult handle; + Exception exception; + InvokeResult result; + + public MethodCall (SoftEvaluationContext ctx, MethodMirror function, object obj, Value[] args, bool enableOutArgs) : base (ctx) + { + this.function = function; + this.obj = obj; + this.args = args; + if (enableOutArgs) { + this.options |= InvokeOptions.ReturnOutArgs; + } + if (function.VirtualMachine.Version.AtLeast (2, 40)) { + this.options |= InvokeOptions.Virtual; + } + } + + public override string Description { + get { + return function.DeclaringType.FullName + "." + function.Name; + } + } + + public override void Invoke () + { + var ctx = (SoftEvaluationContext) EvaluationContext; + + try { + if (obj is ObjectMirror) + handle = ((ObjectMirror)obj).BeginInvokeMethod (ctx.Thread, function, args, options, null, null); + else if (obj is TypeMirror) + handle = ((TypeMirror)obj).BeginInvokeMethod (ctx.Thread, function, args, options, null, null); + else if (obj is StructMirror) + handle = ((StructMirror)obj).BeginInvokeMethod (ctx.Thread, function, args, options | InvokeOptions.ReturnOutThis, null, null); + else if (obj is PrimitiveValue) + handle = ((PrimitiveValue)obj).BeginInvokeMethod (ctx.Thread, function, args, options, null, null); + else + throw new ArgumentException ("Soft debugger method calls cannot be invoked on objects of type " + obj.GetType ().Name); + } catch (InvocationException ex) { + ctx.Session.StackVersion++; + exception = ex; + } catch (Exception ex) { + ctx.Session.StackVersion++; + DebuggerLoggingService.LogError ("Error in soft debugger method call thread on " + GetInfo (), ex); + exception = ex; + } + } + + public override void Abort () + { + var ctx = (SoftEvaluationContext)EvaluationContext; + if (handle is IInvokeAsyncResult) { + var info = GetInfo (); + DebuggerLoggingService.LogMessage ("Aborting invocation of " + info); + ((IInvokeAsyncResult) handle).Abort (); + ctx.Session.StackVersion++; + // Don't wait for the abort to finish. The engine will do it. + } else { + throw new NotSupportedException (); + } + } + + public override void Shutdown () + { + shutdownEvent.Set (); + } + + void EndInvoke () + { + var ctx = (SoftEvaluationContext) EvaluationContext; + + try { + if (obj is ObjectMirror) + result = ((ObjectMirror)obj).EndInvokeMethodWithResult (handle); + else if (obj is TypeMirror) + result = ((TypeMirror)obj).EndInvokeMethodWithResult (handle); + else if (obj is StructMirror) + result = ((StructMirror)obj).EndInvokeMethodWithResult (handle); + else + result = ((PrimitiveValue)obj).EndInvokeMethodWithResult (handle); + } catch (InvocationException ex) { + if (!Aborting && ex.Exception != null) { + string ename = ctx.Adapter.GetValueTypeName (ctx, ex.Exception); + var vref = ctx.Adapter.GetMember (ctx, null, ex.Exception, "Message"); + + exception = vref != null ? new Exception (ename + ": " + (string) vref.ObjectValue) : new Exception (ename); + return; + } + exception = ex; + } catch (Exception ex) { + DebuggerLoggingService.LogError ("Error in soft debugger method call thread on " + GetInfo (), ex); + exception = ex; + } finally { + ctx.Session.StackVersion++; + } + } + + string GetInfo () + { + try { + TypeMirror type = null; + if (obj is ObjectMirror) + type = ((ObjectMirror)obj).Type; + else if (obj is TypeMirror) + type = (TypeMirror)obj; + else if (obj is StructMirror) + type = ((StructMirror)obj).Type; + return string.Format ("method {0} on object {1}", + function == null? "[null]" : function.FullName, + type == null? "[null]" : type.FullName); + } catch (Exception ex) { + DebuggerLoggingService.LogError ("Error getting info for SDB MethodCall", ex); + return ""; + } + } + + public override bool WaitForCompleted (int timeout) + { + if (handle == null) + return true; + int res = WaitHandle.WaitAny (new WaitHandle[] { handle.AsyncWaitHandle, shutdownEvent }, timeout); + if (res == 0) { + EndInvoke (); + return true; + } + // Return true if shut down. + return res == 1; + } + + public Value ReturnValue { + get { + if (exception != null) + throw new EvaluatorException (exception.Message); + return result.Result; + } + } + + public Value[] OutArgs { + get { + if (exception != null) + throw new EvaluatorException (exception.Message); + return result.OutArgs; + } + } + } +} diff --git a/VisualPascalABCNETLinux/MonoDebugging/Soft/SoftDebuggerBacktrace.cs b/VisualPascalABCNETLinux/MonoDebugging/Soft/SoftDebuggerBacktrace.cs new file mode 100644 index 000000000..2c1d1eebc --- /dev/null +++ b/VisualPascalABCNETLinux/MonoDebugging/Soft/SoftDebuggerBacktrace.cs @@ -0,0 +1,232 @@ +// +// SoftDebuggerBacktrace.cs +// +// Authors: Lluis Sanchez Gual +// Jeffrey Stedfast +// +// Copyright (c) 2009 Novell, Inc (http://www.novell.com) +// Copyright (c) 2012 Xamarin Inc. (http://www.xamarin.com) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +using System; +using System.Linq; +using System.Text; +using System.Collections.Generic; +using Mono.Debugging.Client; +using Mono.Debugging.Backend; +using MDB = Mono.Debugger.Soft; +using DC = Mono.Debugging.Client; +using Mono.Debugging.Evaluation; + +namespace Mono.Debugging.Soft +{ + internal class SoftDebuggerStackFrame : Mono.Debugging.Client.StackFrame { + public Mono.Debugger.Soft.StackFrame StackFrame { + get; private set; + } + + public SoftDebuggerStackFrame (Mono.Debugger.Soft.StackFrame frame, string addressSpace, SourceLocation location, string language, bool isExternalCode, bool hasDebugInfo, bool isDebuggerHidden, string fullModuleName, string fullTypeName) + : base (frame.ILOffset, addressSpace, location, language, isExternalCode, hasDebugInfo, isDebuggerHidden, fullModuleName, fullTypeName) + { + StackFrame = frame; + } + } + + public class SoftDebuggerBacktrace: BaseBacktrace + { + readonly SoftDebuggerSession session; + readonly MDB.ThreadMirror thread; + readonly int stackVersion; + MDB.StackFrame[] frames; + + public SoftDebuggerBacktrace (SoftDebuggerSession session, MDB.ThreadMirror thread): base (session.Adaptor) + { + this.session = session; + this.thread = thread; + stackVersion = session.StackVersion; + if (thread != null) + this.frames = thread.GetFrames (); + else + this.frames = new MDB.StackFrame[0]; + } + + void ValidateStack () + { + if (stackVersion != session.StackVersion && thread != null) + frames = thread.GetFrames (); + } + + public override DC.StackFrame[] GetStackFrames (int firstIndex, int lastIndex) + { + ValidateStack (); + + if (lastIndex < 0) + lastIndex = frames.Length - 1; + + List list = new List (); + for (int n = firstIndex; n <= lastIndex && n < frames.Length; n++) + list.Add (CreateStackFrame (frames[n], n)); + + return list.ToArray (); + } + + public override int FrameCount { + get { + ValidateStack (); + return frames.Length; + } + } + + DC.StackFrame CreateStackFrame (MDB.StackFrame frame, int frameIndex) + { + MDB.MethodMirror method = frame.Method; + MDB.TypeMirror type = method.DeclaringType; + string fileName = frame.FileName; + string typeFullName = null; + string typeFQN = null; + string methodName; + + if (fileName != null) + fileName = SoftDebuggerSession.NormalizePath (fileName); + + if (method.VirtualMachine.Version.AtLeast (2, 12) && method.IsGenericMethod) { + StringBuilder name = new StringBuilder (method.Name); + + name.Append ('<'); + + if (method.VirtualMachine.Version.AtLeast (2, 15)) { + bool first = true; + + foreach (var argumentType in method.GetGenericArguments ()) { + if (!first) + name.Append (", "); + + name.Append (session.Adaptor.GetDisplayTypeName (argumentType.FullName)); + first = false; + } + } + + name.Append ('>'); + + methodName = name.ToString (); + } else { + methodName = method.Name; + } + + if (string.IsNullOrEmpty (methodName)) + methodName = "[Function Without Name]"; + + // Compiler generated anonymous/lambda methods + bool special_method = false; + if (methodName [0] == '<' && methodName.Contains (">m__")) { + int nidx = methodName.IndexOf (">m__", StringComparison.Ordinal) + 2; + methodName = "AnonymousMethod" + methodName.Substring (nidx, method.Name.Length - nidx); + special_method = true; + } + + if (type != null) { + string typeDisplayName = session.Adaptor.GetDisplayTypeName (type.FullName); + + if (SoftDebuggerAdaptor.IsGeneratedType (type)) { + // The user-friendly method name is embedded in the generated type name + var mn = SoftDebuggerAdaptor.GetNameFromGeneratedType (type); + + // Strip off the generated type name + int dot = typeDisplayName.LastIndexOf ('.'); + var tname = typeDisplayName.Substring (0, dot); + + // Keep any type arguments + int targs = typeDisplayName.LastIndexOf ('<'); + if (targs > dot + 1) + mn += typeDisplayName.Substring (targs, typeDisplayName.Length - targs); + + typeDisplayName = tname; + + if (special_method) + typeDisplayName += "." + mn; + else + methodName = mn; + } + + methodName = typeDisplayName + "." + methodName; + + typeFQN = type.Module.FullyQualifiedName; + typeFullName = type.FullName; + } + bool external = false; + bool hidden = false; + if (session.VirtualMachine.Version.AtLeast (2, 21)) { + var ctx = GetEvaluationContext (frameIndex, session.EvaluationOptions); + var hiddenAttr = session.Adaptor.GetType (ctx, "System.Diagnostics.DebuggerHiddenAttribute") as MDB.TypeMirror; + if (hiddenAttr != null) + hidden = method.GetCustomAttributes (hiddenAttr, true).Any (); + } + if (hidden) { + external = true; + } else { + external = session.IsExternalCode (frame); + if (!external && session.Options.ProjectAssembliesOnly && session.VirtualMachine.Version.AtLeast (2, 21)) { + var ctx = GetEvaluationContext (frameIndex, session.EvaluationOptions); + var nonUserCodeAttr = session.Adaptor.GetType (ctx, "System.Diagnostics.DebuggerNonUserCodeAttribute") as MDB.TypeMirror; + if (nonUserCodeAttr != null) + external = method.GetCustomAttributes (nonUserCodeAttr, true).Any (); + } + } + + var sourceLink = session.GetSourceLink (frame.Method, frame.FileName); + var location = new DC.SourceLocation (methodName, fileName, frame.LineNumber, frame.ColumnNumber, frame.Location.EndLineNumber, frame.Location.EndColumnNumber, frame.Location.SourceFileHash, sourceLink); + + string addressSpace = string.Empty; + bool hasDebugInfo = false; + string language; + + if (frame.Method != null) { + if (frame.IsNativeTransition) { + language = "Transition"; + } else { + addressSpace = method.FullName; + language = "Managed"; + hasDebugInfo = true; + } + } else { + language = "Native"; + } + + return new SoftDebuggerStackFrame (frame, addressSpace, location, language, external, hasDebugInfo, hidden, typeFQN, typeFullName); + } + + protected override EvaluationContext GetEvaluationContext (int frameIndex, EvaluationOptions options) + { + ValidateStack (); + + if (frameIndex >= frames.Length) + return null; + + var frame = frames[frameIndex]; + + return new SoftEvaluationContext (session, frame, options); + } + + public override AssemblyLine[] Disassemble (int frameIndex, int firstLine, int count) + { + return session.Disassemble (frames [frameIndex], firstLine, count); + } + } +} diff --git a/VisualPascalABCNETLinux/MonoDebugging/Soft/SoftDebuggerSession.cs b/VisualPascalABCNETLinux/MonoDebugging/Soft/SoftDebuggerSession.cs new file mode 100644 index 000000000..27f7d6f68 --- /dev/null +++ b/VisualPascalABCNETLinux/MonoDebugging/Soft/SoftDebuggerSession.cs @@ -0,0 +1,3551 @@ +// +// SoftDebuggerSession.cs +// +// Authors: Lluis Sanchez Gual +// Jeffrey Stedfast +// +// Copyright (c) 2009 Novell, Inc (http://www.novell.com) +// Copyright (c) 2012 Xamarin Inc. (http://www.xamarin.com) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +//#define DEBUG_EVENT_QUEUEING + +using System; +using System.IO; +using System.Net; +using System.Linq; +using System.Text; +using System.Threading; +using System.Reflection; +using System.Net.Sockets; +using System.Globalization; +using System.Collections.Generic; +using System.Runtime.InteropServices; +using System.Text.RegularExpressions; + +using Mono.Debugger.Soft; + +using Mono.Debugging.Client; +using Mono.Debugging.Evaluation; + +using StackFrame = Mono.Debugger.Soft.StackFrame; +using Assembly = Mono.Debugging.Client.Assembly; + +namespace Mono.Debugging.Soft +{ + public class SoftDebuggerSession : DebuggerSession + { + readonly Dictionary> domainAssembliesToUnload = new Dictionary> (); + readonly Dictionary, MethodMirror[]> overloadResolveCache = new Dictionary, MethodMirror[]> (); + readonly Dictionary> source_to_type = new Dictionary> (PathComparer); + readonly Dictionary activeExceptionsByThread = new Dictionary (); + readonly Dictionary breakpoints = new Dictionary (); + readonly Dictionary type_to_source = new Dictionary (); + readonly Dictionary> aliases = new Dictionary> (); + readonly Dictionary> types = new Dictionary> (); + readonly LinkedList> queuedEventSets = new LinkedList> (); + readonly Dictionary localThreadIds = new Dictionary (); + readonly List pending_bes = new List (); + TypeLoadEventRequest typeLoadReq, typeLoadTypeNameReq; + ExceptionEventRequest unhandledExceptionRequest; + Dictionary assemblyPathMap; + Dictionary symbolPathMap; + ThreadMirror current_thread, recent_thread; + List assemblyFilters; + StepEventRequest currentStepRequest; + IConnectionDialog connectionDialog; + Thread outputReader, errorReader; + bool loggedSymlinkedRuntimesBug; + SoftDebuggerStartArgs startArgs; + List userAssemblyNames; + List sourceUpdates; + Mono.Debugging.Client.ThreadInfo[] current_threads; + string remoteProcessName; + long currentAddress = -1; + IAsyncResult connection; + int currentStackDepth; + ProcessInfo[] procs; + Thread eventHandler; + VirtualMachine vm; + bool autoStepInto; + bool disposed; + bool started; + + internal int StackVersion; + + public SoftDebuggerAdaptor Adaptor { + get; private set; + } + + public SoftDebuggerSession () + { + Adaptor = CreateSoftDebuggerAdaptor (); + Adaptor.BusyStateChanged += (sender, e) => SetBusyState (e); + Adaptor.DebuggerSession = this; + sourceUpdates = new List (); + } + + protected virtual SoftDebuggerAdaptor CreateSoftDebuggerAdaptor () + { + return new SoftDebuggerAdaptor (); + } + + public Version ProtocolVersion { + get { return new Version (vm.Version.MajorVersion, vm.Version.MinorVersion); } + } + + protected override void OnRun (DebuggerStartInfo startInfo) + { + if (HasExited) + throw new InvalidOperationException ("Already exited"); + + SetupProcessStartHooks (); + + var dsi = (SoftDebuggerStartInfo) startInfo; + if (dsi.StartArgs is SoftDebuggerLaunchArgs) { + remoteProcessName = Path.GetFileNameWithoutExtension (dsi.Command); + StartLaunching (dsi); + } else if (dsi.StartArgs is SoftDebuggerConnectArgs) { + StartConnecting (dsi); + } else if (dsi.StartArgs is SoftDebuggerListenArgs) { + StartListening (dsi); + } else if (dsi.StartArgs.ConnectionProvider != null) { + StartConnection (dsi); + } else { + throw new ArgumentException ("StartArgs has no ConnectionProvider"); + } + } + + void StartConnection (SoftDebuggerStartInfo dsi) + { + startArgs = dsi.StartArgs; + + RegisterUserAssemblies (dsi); + + if (!String.IsNullOrEmpty (dsi.LogMessage)) + OnDebuggerOutput (false, dsi.LogMessage + Environment.NewLine); + + AsyncCallback callback = null; + int attemptNumber = 0; + int maxAttempts = startArgs.MaxConnectionAttempts; + int timeBetweenAttempts = startArgs.TimeBetweenConnectionAttempts; + callback = delegate (IAsyncResult ar) { + try { + string appName; + VirtualMachine machine; + startArgs.ConnectionProvider.EndConnect (ar, out machine, out appName); + remoteProcessName = appName; + ConnectionStarted (machine); + return; + } catch (Exception ex) { + attemptNumber++; + if (!ShouldRetryConnection (ex, attemptNumber) + || startArgs?.ConnectionProvider?.ShouldRetryConnection (ex) == false + || attemptNumber == maxAttempts + || HasExited) { + OnConnectionError (ex); + return; + } + } + try { + if (timeBetweenAttempts > 0) + Thread.Sleep (timeBetweenAttempts); + ConnectionStarting (startArgs.ConnectionProvider.BeginConnect (dsi, callback), dsi, false, attemptNumber); + } catch (Exception ex2) { + OnConnectionError (ex2); + } + }; + //the "listening" value is never used, pass a dummy value + ConnectionStarting (startArgs.ConnectionProvider.BeginConnect (dsi, callback), dsi, false, 0); + } + + void StartLaunching (SoftDebuggerStartInfo dsi) + { + var args = (SoftDebuggerLaunchArgs) dsi.StartArgs; + var executable = string.IsNullOrEmpty (args.MonoExecutableFileName) ? "mono" : args.MonoExecutableFileName; + var runtime = string.IsNullOrEmpty (args.MonoRuntimePrefix) ? executable : Path.Combine (Path.Combine (args.MonoRuntimePrefix, "bin"), executable); + RegisterUserAssemblies (dsi); + + var psi = new System.Diagnostics.ProcessStartInfo (runtime) { + Arguments = string.Format ("{2} \"{0}\" {1}", dsi.Command, dsi.Arguments, dsi.RuntimeArguments), + WorkingDirectory = dsi.WorkingDirectory, + RedirectStandardOutput = true, + RedirectStandardError = true, + UseShellExecute = false, + CreateNoWindow = true, + }; + + LaunchOptions options = null; + + if (dsi.UseExternalConsole && args.ExternalConsoleLauncher != null) { + options = new LaunchOptions (); + options.CustomTargetProcessLauncher = args.ExternalConsoleLauncher; + psi.RedirectStandardOutput = false; + psi.RedirectStandardError = false; + } + if (args.CustomProcessLauncher != null) { + options = options ?? new LaunchOptions (); + options.CustomProcessLauncher = args.CustomProcessLauncher; + } + + var sdbLog = Environment.GetEnvironmentVariable ("MONODEVELOP_SDB_LOG"); + if (!string.IsNullOrEmpty (sdbLog)) { + options = options ?? new LaunchOptions (); + options.AgentArgs = string.Format ("loglevel=10,logfile='{0}',setpgid=y", sdbLog); + } + + foreach (var env in args.MonoRuntimeEnvironmentVariables) + psi.EnvironmentVariables[env.Key] = env.Value; + + foreach (var env in dsi.EnvironmentVariables) + psi.EnvironmentVariables[env.Key] = env.Value; + + if (!string.IsNullOrEmpty (dsi.LogMessage)) + OnDebuggerOutput (false, dsi.LogMessage + Environment.NewLine); + + var callback = HandleConnectionCallbackErrors (ar => ConnectionStarted (VirtualMachineManager.EndLaunch (ar))); + ConnectionStarting (VirtualMachineManager.BeginLaunch (psi, callback, options), dsi, true, 0); + } + + /// Starts the debugger listening for a connection over TCP/IP + protected void StartListening (SoftDebuggerStartInfo dsi) + { + int dp, cp; + StartListening (dsi, out dp, out cp); + } + + /// Starts the debugger listening for a connection over TCP/IP + protected void StartListening (SoftDebuggerStartInfo dsi, out int assignedDebugPort) + { + int cp; + StartListening (dsi, out assignedDebugPort, out cp); + } + + /// Starts the debugger listening for a connection over TCP/IP + protected void StartListening (SoftDebuggerStartInfo dsi, out int assignedDebugPort, out int assignedConsolePort) + { + IPEndPoint dbgEP, conEP; + InitForRemoteSession (dsi, out dbgEP, out conEP); + + var callback = HandleConnectionCallbackErrors (ar => ConnectionStarted (VirtualMachineManager.EndListen (ar))); + var a = VirtualMachineManager.BeginListen (dbgEP, conEP, callback, out assignedDebugPort, out assignedConsolePort); + ConnectionStarting (a, dsi, true, 0); + } + + protected virtual bool ShouldRetryConnection (Exception ex, int attemptNumber) + { + var sx = ex as SocketException; + if (sx != null) { + if (sx.ErrorCode == 10061) //connection refused + return true; + } + //retry if it receives 0 byte and threw an exception in the handshake + return true; + } + + protected void StartConnecting (SoftDebuggerStartInfo dsi) + { + StartConnecting (dsi, dsi.StartArgs.MaxConnectionAttempts, dsi.StartArgs.TimeBetweenConnectionAttempts); + } + + /// Starts the debugger connecting to a remote IP + protected void StartConnecting (SoftDebuggerStartInfo dsi, int maxAttempts, int timeBetweenAttempts) + { + if (timeBetweenAttempts < 0 || timeBetweenAttempts > 10000) + throw new ArgumentException ("timeBetweenAttempts"); + + IPEndPoint dbgEP, conEP; + InitForRemoteSession (dsi, out dbgEP, out conEP); + + AsyncCallback callback = null; + int attemptNumber = 0; + callback = delegate (IAsyncResult ar) { + try { + ConnectionStarted (VirtualMachineManager.EndConnect (ar)); + return; + } catch (Exception ex) { + attemptNumber++; + if (!ShouldRetryConnection (ex, attemptNumber) || attemptNumber == maxAttempts || HasExited) { + OnConnectionError (ex); + return; + } + } + try { + if (timeBetweenAttempts > 0) + Thread.Sleep (timeBetweenAttempts); + + ConnectionStarting (VirtualMachineManager.BeginConnect (dbgEP, conEP, callback), dsi, false, attemptNumber); + + } catch (Exception ex2) { + OnConnectionError (ex2); + } + }; + + ConnectionStarting (VirtualMachineManager.BeginConnect (dbgEP, conEP, callback), dsi, false, 0); + } + + void InitForRemoteSession (SoftDebuggerStartInfo dsi, out IPEndPoint dbgEP, out IPEndPoint conEP) + { + if (remoteProcessName != null) + throw new InvalidOperationException ("Cannot initialize connection more than once"); + + var args = (SoftDebuggerRemoteArgs) dsi.StartArgs; + + remoteProcessName = args.AppName; + + RegisterUserAssemblies (dsi); + + dbgEP = new IPEndPoint (args.Address, args.DebugPort); + conEP = args.RedirectOutput? new IPEndPoint (args.Address, args.OutputPort) : null; + + if (!String.IsNullOrEmpty (dsi.LogMessage)) + OnDebuggerOutput (false, dsi.LogMessage + Environment.NewLine); + } + + ///Catches errors in async callbacks and hands off to OnConnectionError + AsyncCallback HandleConnectionCallbackErrors (AsyncCallback callback) + { + return delegate (IAsyncResult ar) { + connection = null; + try { + callback (ar); + } catch (Exception ex) { + OnConnectionError (ex); + } + }; + } + + /// + /// Called if an error happens while making the connection. Default terminates the session. + /// + protected virtual void OnConnectionError (Exception ex) + { + //if the exception was caused by cancelling the session + if (HasExited) + return; + + if (!HandleException (new ConnectionException (ex))) { + DebuggerLoggingService.LogAndShowException ("Unhandled error launching soft debugger", ex); + } + + // The session is dead + // HandleException doesn't actually handle exceptions, it just displays them. + try { + EndSession (); + } catch (Exception e) { + DebuggerLoggingService.LogError ("Unhandled error ending the debugger session", e); + } + } + + void ConnectionStarting (IAsyncResult connectionHandle, DebuggerStartInfo dsi, bool listening, int attemptNumber) + { + if (connection != null && (attemptNumber == 0 || !connection.IsCompleted)) + throw new InvalidOperationException ("Already connecting"); + + connection = connectionHandle; + + if (attemptNumber == 0) { + if (ConnectionDialogCreatorExtended != null) + connectionDialog = ConnectionDialogCreatorExtended (dsi); + else if (ConnectionDialogCreator != null) + connectionDialog = ConnectionDialogCreator (); + + if (connectionDialog != null) { + connectionDialog.UserCancelled += delegate { + EndSession (); + }; + } + } + + if (connectionDialog != null) + connectionDialog.SetMessage (dsi, GetConnectingMessage (dsi), listening, attemptNumber); + } + + protected virtual string GetConnectingMessage (DebuggerStartInfo dsi) + { + return null; + } + + void EndLaunch () + { + HideConnectionDialog (); + if (connection != null) { + try { + if (startArgs != null && startArgs.ConnectionProvider != null) { + startArgs.ConnectionProvider.CancelConnect(connection); + startArgs = null; + } else { + VirtualMachineManager.CancelConnection(connection); + } + } catch(Exception e) { + DebuggerLoggingService.LogError("Unhandled error canceling the debugger connection", e); + } + connection = null; + } + } + + protected virtual void EndSession () + { + if (!HasExited) { + EndLaunch (); + OnTargetEvent (new TargetEventArgs (TargetEventType.TargetExited)); + } + } + + const string StartWithShellExecuteExMarker = "___Process_StartWithShellExecuteEx_Marker___"; + const string StartWithCreateProcessMarker = "___Process_StartWithCreateProcess_Marker__"; + const string SetProcessIdMarker = "___Process_SetProcessId_Marker__"; + + void SetupProcessStartHooks () + { + if (!Options.DebugSubprocesses) + return; + + // Subprocess lauch is intercepted using 3 function breakpoints. + // The first breakpoint is used to intercept calls to start a process with shell execute. + var bp = Breakpoints.OfType< FunctionBreakpoint> ().FirstOrDefault (b => b.CustomActionId == StartWithShellExecuteExMarker); + if (bp == null) { + bp = new FunctionBreakpoint ("System.Diagnostics.Process.StartWithShellExecuteEx", "C#"); + bp.HitAction = HitAction.CustomAction; + bp.CustomActionId = StartWithShellExecuteExMarker; + bp.NonUserBreakpoint = true; + Breakpoints.Add (bp); + } + + // The second breakpoint is used to intercept calls to start a process with CreateProcess + bp = Breakpoints.OfType ().FirstOrDefault (b => b.CustomActionId == StartWithCreateProcessMarker); + if (bp == null) { + bp = new FunctionBreakpoint ("System.Diagnostics.Process.StartWithCreateProcess", "C#"); + bp.HitAction = HitAction.CustomAction; + bp.CustomActionId = StartWithCreateProcessMarker; + bp.NonUserBreakpoint = true; + Breakpoints.Add (bp); + } + + // The third breakpoint is used to intercept the method that assigns an ID to the process. This is used to + // retrieve the process id, and also as a signal that the process has started running + bp = Breakpoints.OfType ().FirstOrDefault (b => b.CustomActionId == SetProcessIdMarker); + if (bp == null) { + bp = new FunctionBreakpoint ("System.Diagnostics.Process.SetProcessId", "C#"); + bp.HitAction = HitAction.CustomAction; + bp.CustomActionId = SetProcessIdMarker; + bp.NonUserBreakpoint = true; + Breakpoints.Add (bp); + } + } + + bool HandleProcessStartHook (ThreadMirror thread, BreakEvent b) + { + if (!Options.DebugSubprocesses) + return false; + + // Subprocess debugging is achieved by intercepting calls to start a process using function breakpoints. + // This is achieved in 3 stages: + // 1) Process start interception: this is done by adding a function breakpoint to Process.StartWithShellExecuteEx + // and Process.StartWithCreateProcess. If the process being launched is a mono process a new debug session + // is creaated, and the process start info object is patched to add the debugger agent configuration options. + // 2) Process launch. This is actually done by the current executing process. The new process is bound to + // the new debug session. However, to complete the debug session initialization we need an actual reference + // to the process, so that the session can get the process id and subscribe process events, and that's + // done in step 3. + // 3) Process launched interception: this is done by adding a function breakpoint to Process.SetProcessId. + // This method is called after starting the process, so at this point it is possible to get a reference + // to the running process object and assign it to the debug session to complete the initialization. + + // A potential process start call has been intercepted. + + var evalOptions = EvaluationOptions.DefaultOptions.Clone (); + evalOptions.AllowTargetInvoke = evalOptions.AllowMethodEvaluation = evalOptions.FlattenHierarchy = true; + evalOptions.GroupPrivateMembers = evalOptions.GroupStaticMembers = evalOptions.AllowToStringCalls = evalOptions.EllipsizeStrings = false; + + if (b.CustomActionId == StartWithShellExecuteExMarker || b.CustomActionId == StartWithCreateProcessMarker) { + + // A project start call has been intercepted. We need to check if a mono process is being started. + + var fr = thread.GetFrames ()[0]; + var ctx = new SoftEvaluationContext (this, fr, evalOptions); + var eval = ctx.Evaluator; + + var parameters = eval.GetParameters (ctx); + var startInfo = parameters.First(); + var exe = (string) startInfo.GetChild ("FileName", evalOptions).GetRawValue (evalOptions); + + // The process being launched is a mono process if the target file is a .exe or .dll, or if the launcher is mono. + + var ext = Path.GetExtension (exe).ToLower (); + if (ext == ".exe" || ext == ".dll" || Subprocess.IsMonoLauncher (exe)) { + + // If the runtime arguments are already setting up a debugger agent, don't override it + var arguments = (string)startInfo.GetChild ("Arguments", evalOptions).GetRawValue (evalOptions); + if (arguments.Contains ("--debugger-agent")) + return false; + + // Create the new debug session + Subprocess sp; + lock (subprocessesStarting) { + sp = new Subprocess (GetId (thread), this); + subprocessesStarting.Add (sp); + } + + if (sp.CreateSession (startInfo, evalOptions)) { + // Notify that the new session has started + OnSubprocessStarted (sp.SubprocessSession); + } + } + return true; + } else if (b.CustomActionId == SetProcessIdMarker) { + + // A call to SetProcessId has been intercepted + // Check if there is a subprocess waiting for the start signal + + var tid = GetId (thread); + Subprocess subprocess; + + lock (subprocessesStarting) + subprocess = subprocessesStarting.FirstOrDefault (s => s.ThreadId == tid); + + if (subprocess != null) { + lock (subprocessesStarting) + subprocessesStarting.Remove (subprocess); + + var fr = thread.GetFrames ()[0]; + var ctx = new SoftEvaluationContext (this, fr, evalOptions); + var eval = ctx.Evaluator; + + try { + // The first parameter is the process ID + var parameters = eval.GetParameters (ctx); + var id = (int)parameters.First ().GetRawValue (evalOptions); + + // Get the start info object from the process + var ts = eval.GetThisReference (ctx); + var si = ts.GetChild ("startInfo", evalOptions); + + // Tell the subprocess that the process has started. This will finish the initialization + // of the debug session. + subprocess.SetStarted (si, id, ctx); + } catch { + subprocess.Shutdown (); + throw; + } + } + return true; + } + return false; + } + + List subprocessesStarting = new List (); + + public Dictionary, MethodMirror[]> OverloadResolveCache { + get { + return overloadResolveCache; + } + } + + void HideConnectionDialog () + { + if (connectionDialog != null) { + connectionDialog.Dispose (); + connectionDialog = null; + } + } + + /// + /// If subclasses do an async connect in OnRun, they should pass the resulting VM to this method. + /// If the vm is null, the session will be closed. + /// + void ConnectionStarted (VirtualMachine machine) + { + if (vm != null) + throw new InvalidOperationException ("The VM has already connected"); + + if (machine == null) { + EndSession (); + return; + } + + connection = null; + + vm = machine; + + // Allocate the process list now, so it is cached and can be queried while the session is running + OnGetProcesses (); + + ConnectOutput (machine.StandardOutput, false); + ConnectOutput (machine.StandardError, true); + + HideConnectionDialog (); + + machine.EnableEvents (EventType.AssemblyLoad, EventType.ThreadStart, EventType.ThreadDeath, + EventType.AppDomainUnload, EventType.UserBreak, EventType.UserLog); + try { + unhandledExceptionRequest = machine.CreateExceptionRequest (null, false, true); + unhandledExceptionRequest.Enable (); + } catch (NotSupportedException) { + //Mono < 2.6.3 doesn't support catching unhandled exceptions + } + + if (machine.Version.AtLeast (2, 9)) { + /* Created later */ + } else { + machine.EnableEvents (EventType.TypeLoad); + } + + + machine.EnableEvents (EventType.MethodUpdate); + + started = true; + + /* Wait for the VMStart event */ + HandleEventSet (machine.GetNextEventSet ()); + + eventHandler = new Thread (EventHandler); + eventHandler.Name = "SDB Event Handler"; + eventHandler.IsBackground = true; + eventHandler.Start (); + } + + void RegisterUserAssemblies (SoftDebuggerStartInfo dsi) + { + if (Options.ProjectAssembliesOnly && dsi.UserAssemblyNames != null) { + assemblyFilters = new List (); + userAssemblyNames = dsi.UserAssemblyNames.Select (x => x.ToString ()).ToList (); + } + + assemblyPathMap = dsi.AssemblyPathMap; + if (assemblyPathMap == null) + assemblyPathMap = new Dictionary (); + + if (dsi.SymbolPathMap == null) + symbolPathMap = new Dictionary(); + else + symbolPathMap = dsi.SymbolPathMap; + } + + public void AddOrReplaceAssemblyPathMap(string assembly, string path) + { + assemblyPathMap[assembly] = path; + } + + public void AddOrReplaceSymbolPathMap (string assembly, string path) + { + symbolPathMap[assembly] = path; + } + + protected bool SetSocketTimeouts (int sendTimeout, int receiveTimeout, int keepaliveInterval) + { + try { + if (vm.Version.AtLeast (2, 4)) { + vm.EnableEvents (EventType.KeepAlive); + vm.SetSocketTimeouts (sendTimeout, receiveTimeout, keepaliveInterval); + return true; + } + + return false; + } catch { + return false; + } + } + + protected void ConnectOutput (StreamReader reader, bool error) + { + Thread t = (error ? errorReader : outputReader); + if (t != null || reader == null) + return; + + t = new Thread (() => ReadOutput (reader, error)); + t.Name = error ? "SDB error reader" : "SDB output reader"; + t.IsBackground = true; + t.Start (); + + if (error) + errorReader = t; + else + outputReader = t; + } + + void ReadOutput (TextReader reader, bool isError) + { + try { + var buffer = new char [1024]; + while (!HasExited) { + int c = reader.Read (buffer, 0, buffer.Length); + if (c > 0) { + OnTargetOutput (isError, new string (buffer, 0, c)); + } else { + //FIXME: workaround for buggy console stream that never blocks + Thread.Sleep (250); + } + } + } catch (IOException) { + // Ignore + } + } + + protected virtual void OnResumed () + { + current_threads = null; + current_thread = null; + activeExceptionsByThread.Clear (); + } + + public VirtualMachine VirtualMachine { + get { return vm; } + } + + public TypeMirror GetType (string fullName) + { + List typesList; + + if (!types.TryGetValue (fullName, out typesList)) + aliases.TryGetValue (fullName, out typesList); + if (typesList == null) + return null; + if (typesList.Count == 1) + return typesList [0]; + //Idea here is... If we have multiple types with same name... they must be in different .dlls + //so find 1st matching assembly with stackframes and then return type that belongs to that assembly + var assembly = current_thread.GetFrames ().Select (f => f.Method.DeclaringType.Assembly).FirstOrDefault (asm => typesList.Select (t => t.Assembly).Contains (asm)); + return typesList.FirstOrDefault (t => t.Assembly == assembly) ?? typesList.FirstOrDefault (); + } + + public IEnumerable GetAllTypes () + { + return types.Values.SelectMany (l => l); + } + + protected override bool AllowBreakEventChanges { + get { return true; } + } + + public override void Dispose () + { + base.Dispose (); + + if (disposed) + return; + + disposed = true; + + lock (subprocessesStarting) { + foreach (var s in subprocessesStarting) + s.Shutdown (); + } + + if (!HasExited) + EndLaunch (); + + exceptionRequests.Clear(); + + if (!HasExited) { + if (vm != null) { + ThreadPool.QueueUserWorkItem (delegate { + try { + vm.Exit (0); + } catch (VMDisconnectedException) { + } catch (Exception ex) { + DebuggerLoggingService.LogError ("Error exiting SDB VM:", ex); + } + }); + } + } + + Adaptor.Dispose (); + } + + protected override void OnAttachToProcess (long processId) + { + throw new NotSupportedException (); + } + + protected override void OnContinue () + { + ThreadPool.QueueUserWorkItem (delegate { + try { + Adaptor.CancelAsyncOperations (); // This call can block, so it has to run in background thread to avoid keeping the main session lock + OnResumed (); + vm.Resume (); + DequeueEventsForFirstThread (); + } catch (Exception ex) { + if (!HandleException (ex) && outputOptions.ExceptionMessage) + OnDebuggerOutput (true, ex.ToString ()); + } + }); + } + + protected override void OnDetach () + { + vm.Detach (); + } + + protected override void OnExit () + { + HasExited = true; + EndLaunch (); + if (vm != null) { + try { + vm.Exit (0); + } catch (VMDisconnectedException) { + // The VM was already disconnected, ignore. + } catch (SocketException se) { + // This will often happen during normal operation + DebuggerLoggingService.LogError ("Error closing debugger session", se); + } catch (IOException ex) { + // This will often happen during normal operation + DebuggerLoggingService.LogError ("Error closing debugger session", ex); + } + } + QueueEnsureExited (); + } + + void QueueEnsureExited () + { + if (vm != null) { + //FIXME: this might never get reached if the IDE is Exited first + try { + if (vm.Process != null) { + ThreadPool.QueueUserWorkItem (delegate { + // This is a workaround for a mono bug + // Without this call, the process may become zombie in mono < 2.10.2 + vm.Process.WaitForExit (); + }); + } + } catch (InvalidOperationException) { + // ignore - this is thrown by the vm.Process getter when the process has already exited + } catch (Exception ex) { + DebuggerLoggingService.LogError ("Failed to launch a thread to wait for the process to exit", ex); + } + + var t = new System.Timers.Timer (); + t.Interval = 3000; + t.Elapsed += delegate { + try { + t.Enabled = false; + t.Dispose (); + EnsureExited (); + } catch (Exception ex) { + DebuggerLoggingService.LogError ("Failed to force-terminate process", ex); + } + + try { + if (vm != null) { + //this is a no-op if it already closed + vm.ForceDisconnect (); + } + } catch (Exception ex) { + DebuggerLoggingService.LogError ("Failed to force-close debugger connection", ex); + } + }; + + t.Enabled = true; + } + } + + /// This is a fallback in case the debugger agent doesn't respond to an exit call + protected virtual void EnsureExited () + { + try { + if (vm != null && vm.TargetProcess != null && !vm.TargetProcess.HasExited) + vm.TargetProcess.Kill (); + } catch (Exception ex) { + DebuggerLoggingService.LogError ("Error force-terminating soft debugger process", ex); + } + } + + protected override void OnFinish () + { + Step (StepDepth.Out, StepSize.Line); + } + + protected override ProcessInfo[] OnGetProcesses () + { + if (procs == null) { + if (vm == null)//process didn't start yet + return new ProcessInfo [0]; + if (vm.TargetProcess == null) { + procs = new [] { new ProcessInfo (0, remoteProcessName ?? "mono") }; + } else { + try { + procs = new [] { new ProcessInfo (vm.TargetProcess.Id, remoteProcessName ?? vm.TargetProcess.ProcessName) }; + } catch (Exception ex) { + if (!loggedSymlinkedRuntimesBug) { + loggedSymlinkedRuntimesBug = true; + DebuggerLoggingService.LogError ("Error getting debugger process info. Known Mono bug with symlinked runtimes.", ex); + } + procs = new [] { new ProcessInfo (0, "mono") }; + } + } + } + return new [] { new ProcessInfo (procs[0].Id, procs[0].Name) }; + } + + internal PortablePdbData GetPdbData (AssemblyMirror asm) + { + string pdbFileName; + if (!symbolPathMap.TryGetValue(asm.GetName().FullName, out pdbFileName) || Path.GetExtension(pdbFileName) != ".pdb") + { + string assemblyFileName; + if (!assemblyPathMap.TryGetValue (asm.GetName ().FullName, out assemblyFileName)) + assemblyFileName = asm.Location; + pdbFileName = Path.ChangeExtension (assemblyFileName, ".pdb"); + } + if (PortablePdbData.IsPortablePdb (pdbFileName)) + return new PortablePdbData (pdbFileName); + // Attempt to fetch pdb from the debuggee over the wire + var pdbBlob = asm.GetPdbBlob (); + return pdbBlob != null ? new PortablePdbData (pdbBlob) : null; + } + + internal PortablePdbData GetPdbData (MethodMirror method) + { + return GetPdbData (method.DeclaringType.Assembly); + } + + readonly Dictionary SourceLinkCache = new Dictionary (); + SourceLinkMap [] GetSourceLinkMaps (MethodMirror method) + { + var asm = method.DeclaringType.Assembly; + SourceLinkMap[] maps; + + lock (SourceLinkCache) { + if (SourceLinkCache.TryGetValue (asm, out maps)) + return maps; + } + + string jsonString = null; + + if (asm.VirtualMachine.Version.AtLeast (2, 48)) { + jsonString = asm.ManifestModule.SourceLink; + } else { + // Read the pdb ourselves + jsonString = GetPdbData (asm)?.GetSourceLinkBlob (); + } + + /*if (!string.IsNullOrWhiteSpace(jsonString)) { + try { + var jsonSourceLink = JsonConvert.DeserializeObject (jsonString); + + if (jsonSourceLink != null && jsonSourceLink.Maps != null && jsonSourceLink.Maps.Any ()) + maps = jsonSourceLink.Maps.Select (kv => new SourceLinkMap (kv.Key, kv.Value)).ToArray (); + } catch (JsonException ex) { + DebuggerLoggingService.LogError ("Error reading source link", ex); + } + }*/ + + lock (SourceLinkCache) { + SourceLinkCache[asm] = maps ?? Array.Empty (); + } + + return maps; + } + + internal SourceLink GetSourceLink (MethodMirror method, string originalFileName) + { + if (originalFileName == null) + return null; + + var maps = GetSourceLinkMaps (method); + if (maps == null || maps.Length == 0) + return null; + + originalFileName = originalFileName.Replace ('\\', '/'); + foreach (var map in maps) { + var pattern = map.RelativePathWildcard.Replace ("*", "").Replace ('\\', '/'); + + if (originalFileName.StartsWith (pattern, StringComparison.Ordinal)) { + var localPath = originalFileName.Replace (pattern.Replace (".*", ""), ""); + var httpBasePath = map.UriWildcard.Replace ("*", ""); + // org/project-name/git-sha (usually) + var pathAndQuery = new Uri (httpBasePath).GetComponents (UriComponents.Path, UriFormat.SafeUnescaped); + // org/projectname/git-sha/path/to/file.cs + var relativePath = Path.Combine (pathAndQuery, localPath); + // Replace something like "f:/build/*" with "https://raw.githubusercontent.com/org/projectname/git-sha/*" + var httpPath = Regex.Replace (originalFileName, pattern, httpBasePath); + return new SourceLink (httpPath, relativePath); + } + } + return null; + } + + protected override Backtrace OnGetThreadBacktrace (long processId, long threadId) + { + return GetThreadBacktrace (GetThread (threadId)); + } + + protected override long OnGetElapsedTime (long processId, long threadId) + { + return GetElapsedTime (GetThread (threadId)); + } + + Backtrace GetThreadBacktrace (ThreadMirror thread) + { + return new Backtrace (new SoftDebuggerBacktrace (this, thread)); + } + + long GetElapsedTime (ThreadMirror thread) + { + return thread.ElapsedTime (); + } + + string GetThreadName (ThreadMirror t) + { + string name = t.Name; + if (string.IsNullOrEmpty (name)) { + try { + if (t.IsThreadPoolThread) + return ""; + } catch { + if (vm.Version.AtLeast (2, 2)) { + throw; + } + return ""; + } + } + return name; + } + + protected override void OnFetchFrames (Mono.Debugging.Client.ThreadInfo [] threads) + { + var mirrorThreads = new List (threads.Length); + for (int i = 0; i < threads.Length; i++) { + var thread = GetThread (threads [i].Id); + if (thread != null) { + mirrorThreads.Add (thread); + } + } + ThreadMirror.FetchFrames (mirrorThreads); + } + + protected override Mono.Debugging.Client.ThreadInfo[] OnGetThreads (long processId) + { + if (current_threads == null) { + var mirrors = vm.GetThreads (); + var threads = new Mono.Debugging.Client.ThreadInfo[mirrors.Count]; + + for (int i = 0; i < mirrors.Count; i++) { + var thread = mirrors[i]; + + threads[i] = new Mono.Debugging.Client.ThreadInfo (processId, GetId (thread), GetThreadName (thread), null); + } + + current_threads = threads; + } + + return current_threads; + } + + ThreadMirror GetThread (long threadId) + { + foreach (var thread in vm.GetThreads ()) { + if (GetId (thread) == threadId) + return thread; + } + + return null; + } + + Mono.Debugging.Client.ThreadInfo GetThread (ProcessInfo process, ThreadMirror thread) + { + long id = GetId (thread); + + foreach (var threadInfo in OnGetThreads (process.Id)) { + if (threadInfo.Id == id) + return threadInfo; + } + + return null; + } + + public override bool CanSetNextStatement { + get { return vm.Version.AtLeast (2, 29); } + } + + protected override void OnSetNextStatement (long threadId, string fileName, int line, int column) + { + if (!CanSetNextStatement) + throw new NotSupportedException (); + + var thread = GetThread (threadId); + if (thread == null) + throw new ArgumentException ("Unknown thread."); + + var frames = thread.GetFrames (); + if (frames.Length == 0) + throw new NotSupportedException (); + + bool dummy = false; + var location = FindLocationByMethod (frames[0].Method, fileName, line, column, ref dummy); + if (location == null) + throw new NotSupportedException ("Unable to set the next statement. The next statement cannot be set to another function."); + try { + thread.SetIP (location); + currentAddress = location.ILOffset; + currentStackDepth = frames.Length; + } catch (ArgumentException) { + throw new NotSupportedException (); + } + } + + protected override void OnSetNextStatement (long threadId, int ilOffset) + { + if (!CanSetNextStatement) + throw new NotSupportedException (); + + var thread = GetThread (threadId); + if (thread == null) + throw new ArgumentException ("Unknown thread."); + + var frames = thread.GetFrames (); + if (frames.Length == 0) + throw new NotSupportedException (); + + var location = frames[0].Method.LocationAtILOffset (ilOffset); + if (location == null) + throw new NotSupportedException (); + + try { + thread.SetIP (location); + StackVersion++; + RaiseStopEvent (); + } catch (ArgumentException) { + throw new NotSupportedException (); + } + } + + protected override BreakEventInfo OnInsertBreakEvent (BreakEvent breakEvent) + { + var breakInfo = new BreakInfo (); + + if (HasExited) { + breakInfo.SetStatus (BreakEventStatus.Disconnected, null); + return breakInfo; + } + + if (breakEvent is FunctionBreakpoint function) { + foreach (var method in FindMethodsByName (function.TypeName, function.MethodName, function.ParamTypes)) { + if (!ResolveFunctionBreakpoint (breakInfo, function, method)) { + breakInfo.SetStatus (BreakEventStatus.NotBound, null); + } + } + + breakInfo.TypeName = function.TypeName; + + lock (pending_bes) { + pending_bes.Add (breakInfo); + } + } else if (breakEvent is InstructionBreakpoint instruction) { + Location location; + + breakInfo.FileName = instruction.FileName; + + if ((location = FindLocationByILOffset (instruction, instruction.FileName, out _, out var insideTypeRange)) != null) { + breakInfo.Location = location; + InsertBreakpoint (instruction, breakInfo); + breakInfo.SetStatus (BreakEventStatus.Bound, null); + } else if (insideTypeRange) + breakInfo.SetStatus (BreakEventStatus.Invalid, null); + else + breakInfo.SetStatus (BreakEventStatus.NotBound, null); + + lock (pending_bes) { + pending_bes.Add (breakInfo); + } + } else if (breakEvent is Breakpoint breakpoint) { + bool insideLoadedRange; + bool found = false; + + breakInfo.FileName = breakpoint.FileName; + + foreach (var location in FindLocationsByFile (breakpoint.FileName, breakpoint.Line, breakpoint.Column, out _, out insideLoadedRange)) { + OnDebuggerOutput (false, string.Format ("Resolved pending breakpoint at '{0}:{1},{2}' to {3} [0x{4:x5}].\n", + breakpoint.FileName, breakpoint.Line, breakpoint.Column, + GetPrettyMethodName (location.Method), location.ILOffset)); + + breakInfo.Location = location; + InsertBreakpoint (breakpoint, breakInfo); + found = true; + breakInfo.SetStatus (BreakEventStatus.Bound, null); + } + lock (pending_bes) { + pending_bes.Add (breakInfo); + } + + if (!found) { + breakInfo.Breakpoint = breakpoint; + if (insideLoadedRange) + breakInfo.SetStatus (BreakEventStatus.Invalid, null); + else + breakInfo.SetStatus (BreakEventStatus.NotBound, null); + } + } else if (breakEvent is Catchpoint catchpoint) { + if (!types.ContainsKey (catchpoint.ExceptionName)) { + // + // Same as in FindLocationByFile (), fetch types matching the type name + if (vm.Version.AtLeast (2, 9)) { + foreach (TypeMirror t in vm.GetTypes (catchpoint.ExceptionName, false)) + ProcessType (t); + } + } + + List typesList; + if (types.TryGetValue (catchpoint.ExceptionName, out typesList)) { + foreach (var type in typesList) + InsertCatchpoint (catchpoint, breakInfo, type); + breakInfo.SetStatus (BreakEventStatus.Bound, null); + } else { + breakInfo.SetStatus (BreakEventStatus.NotBound, null); + } + + breakInfo.TypeName = catchpoint.ExceptionName; + lock (pending_bes) { + pending_bes.Add (breakInfo); + } + } + UpdateTypeLoadFilters (); + return breakInfo; + } + + void UpdateTypeLoadFilters() + { + /* + * TypeLoad events lead to too much wire traffic + suspend/resume work, so + * filter them using the file names used by pending breakpoints. + */ + if (vm.Version.AtLeast (2, 9)) { + string [] sourceFileList; + lock (pending_bes) { + sourceFileList = pending_bes.Where (b => b.FileName != null).SelectMany ((b, i) => new [] { + Path.GetFileName (b.FileName), + b.FileName + }).Distinct ().ToArray (); + } + if (sourceFileList.Length > 0) { + //HACK: with older versions of sdb that don't support case-insenitive compares, + //explicitly try lowercased drivename on windows, since csc (when not hosted in VS) lowercases + //the drivename in the pdb files that get converted to mdbs as-is + if (IsWindows && !vm.Version.AtLeast (2, 12)) { + int originalCount = sourceFileList.Length; + Array.Resize (ref sourceFileList, originalCount * 2); + for (int i = 0; i < originalCount; i++) { + string n = sourceFileList[i]; + sourceFileList[originalCount + i] = char.ToLower (n[0]) + n.Substring (1); + } + } + + if (typeLoadReq == null) { + typeLoadReq = vm.CreateTypeLoadRequest (); + } + typeLoadReq.Enabled = false; + typeLoadReq.SourceFileFilter = sourceFileList; + typeLoadReq.Enabled = true; + } + + string [] typeNameList; + lock (pending_bes) { + typeNameList = pending_bes.Where (b => b.TypeName != null).Select (b => b.TypeName).ToArray (); + } + if (typeNameList.Length > 0) { + // Use a separate request since the filters are ANDed together + if (typeLoadTypeNameReq == null) { + typeLoadTypeNameReq = vm.CreateTypeLoadRequest (); + } + typeLoadTypeNameReq.Enabled = false; + typeLoadTypeNameReq.TypeNameFilter = typeNameList; + typeLoadTypeNameReq.Enabled = true; + } + } + } + + private Location FindLocationByILOffset (InstructionBreakpoint bp, string filename, out bool isGeneric, out bool insideTypeRange) + { + var typesInFile = new List (); + + AddFileToSourceMapping (filename); + + insideTypeRange = true; + isGeneric = false; + + lock (source_to_type) { + if (source_to_type.TryGetValue (filename, out typesInFile)) { + foreach (var type in typesInFile) { + var method = type.GetMethod (bp.MethodName); + if (method != null) { + foreach (var location in method.Locations) { + if (location.ILOffset == bp.ILOffset) { + isGeneric = type.IsGenericType; + return location; + } + } + } + } + } + } + + return null; + } + + protected override void OnRemoveBreakEvent (BreakEventInfo eventInfo) + { + if (HasExited) + return; + + var bi = (BreakInfo) eventInfo; + if (bi.Requests.Count != 0) { + foreach (var request in bi.Requests.Keys) { + request.Enabled = false; + breakpoints.Remove (request); + } + + RemoveQueuedBreakEvents (bi.Requests); + } + + lock (pending_bes) { + pending_bes.Remove (bi); + } + } + + protected override void OnEnableBreakEvent (BreakEventInfo eventInfo, bool enable) + { + if (HasExited) + return; + + var bi = (BreakInfo) eventInfo; + if (bi.Requests.Count != 0) { + foreach (var request in bi.Requests.Keys) + request.Enabled = enable; + + if (!enable) + RemoveQueuedBreakEvents (bi.Requests); + } + } + + protected override void OnUpdateBreakEvent (BreakEventInfo eventInfo) + { + } + + void InsertBreakpoint (Breakpoint bp, BreakInfo bi) + { + InsertBreakpoint (bp, bi, bi.Location.Method, bi.Location.ILOffset); + } + + void UpdateBreakpoint (Breakpoint bp, BreakInfo bi) + { + foreach (var req in bi.Requests) + { + req.Key.Disable (); + var request = vm.SetBreakpoint (bi.Location.Method, bi.Location.ILOffset); + req.Key.UpdateReqId (request.GetId()); + req.Key.Enabled = bp.Enabled; + } + } + + void InsertBreakpoint (Breakpoint bp, BreakInfo bi, MethodMirror method, int ilOffset) + { + EventRequest request; + + request = vm.SetBreakpoint (method, ilOffset); + request.Enabled = bp.Enabled; + bi.Requests.Add (request, method.DeclaringType.Assembly); + + breakpoints [request] = bi; + + if (bi.Location != null && (bi.Location.LineNumber != bp.Line || bi.Location.ColumnNumber != bp.Column)) + bi.AdjustBreakpointLocation (bi.Location.LineNumber, bi.Location.ColumnNumber); + } + + void InsertCatchpoint (Catchpoint cp, BreakInfo bi, TypeMirror excType) + { + ExceptionEventRequest request; + + request = vm.CreateExceptionRequest (excType, true, true); + //Commenting Count so we have better control of counting + //because VM only allows count equal to some number but we need also + //lower, greater, equal or greater... + //Plus some day we might want to put filtering before counting... + //request.Count = cp.HitCount; // Note: need to set HitCount *before* enabling + if (vm.Version.AtLeast (2, 25)) + request.IncludeSubclasses = cp.IncludeSubclasses; // Note: need to set IncludeSubclasses *before* enabling + request.Enabled = cp.Enabled; + bi.Requests.Add (request, excType.Assembly); + + breakpoints[request] = bi; + } + + #region ExceptionRequest + //VS in Windows doesn't have the concept of a Catchpoint. In the past we did use it anyway, but that leads to all sort of issues and bugs. + //The most important difference is that subclasses are never caught, and that Others is a concept that the Catchpoint doesn't support properly. + //This region is for Windows, to be able to properly handle exceptions with an exception list. The concept of Other is supported starting with vm version 2.54. + + readonly Dictionary exceptionRequests = new Dictionary(); + ExceptionEventRequest otherExceptions; + + public void EnableException(string exceptionType, bool caught = true) + { + lock (exceptionRequests) { + if (!types.ContainsKey (exceptionType)) { + if (vm.Version.AtLeast (2, 9)) { + try { + foreach (TypeMirror t in vm.GetTypes (exceptionType, false)) + ProcessType (t); + } + catch (CommandException exc) { + if (outputOptions.ExceptionMessage) + OnDebuggerOutput (false, string.Format ("Error while parsing type ‘{0}’.\n", exceptionType)); + } + } + } + + if (types.TryGetValue(exceptionType, out var typemirrors)) { + var exception = typemirrors.First(); + var request = vm.CreateExceptionRequest (exception, caught, true, false); + request.Enable(); + exceptionRequests[exceptionType] = request; + } + } + } + + public void EnableOtherExceptions() + { + if (!vm.Version.AtLeast(2, 54)) + return; + + if (otherExceptions == null) + otherExceptions = vm.CreateExceptionRequest (null, true, true, true); + otherExceptions.Enable (); + } + + public void DisableException(string exceptionType, bool setuncaught = true) + { + lock (exceptionRequests) { + if (exceptionRequests.TryGetValue (exceptionType, out var request)) { + request.Disable(); + exceptionRequests.Remove(exceptionType); + } + if (setuncaught) + EnableException (exceptionType, false); + } + } + + public void DisableOtherExceptions() + { + if (otherExceptions != null) { + otherExceptions.Disable(); + } + } + + public void RemoveException(string exceptionType) + { + DisableException(exceptionType, false); + } + + #endregion + + static bool CheckTypeName (string typeName, string name) + { + // if the name provided is empty, it matches anything. + if (string.IsNullOrEmpty (name)) + return true; + + if (name.StartsWith ("global::", StringComparison.Ordinal)) { + if (typeName != name.Substring ("global::".Length)) + return false; + } else if (name.StartsWith ("::", StringComparison.Ordinal)) { + if (typeName != name.Substring ("::".Length)) + return false; + } else { + // be a little more flexible with what we match... i.e. "Console" should match "System.Console" + if (typeName != null && typeName.Length > name.Length) { + if (!typeName.EndsWith (name, StringComparison.Ordinal)) + return false; + + char delim = typeName[typeName.Length - name.Length]; + if (delim != '.' && delim != '+') + return false; + } else if (typeName != name) { + return false; + } + } + + return true; + } + + static bool CheckTypeName (TypeMirror type, string name) + { + if (string.IsNullOrEmpty (name)) { + // empty name matches anything + return true; + } + + if (name[name.Length - 1] == '?') { + // canonicalize the user-specified nullable type + return CheckTypeName (type, string.Format ("System.Nullable<{0}>", name.Substring (0, name.Length - 1))); + } + + if (type.IsArray) { + int startIndex = name.LastIndexOf ('['); + int endIndex = name.Length - 1; + + if (startIndex == -1 || name[endIndex] != ']') { + // the user-specified type is not an array + return false; + } + + var rank = name.Substring (startIndex + 1, endIndex - (startIndex + 1)).Split (new [] { ',' }); + if (rank.Length != type.GetArrayRank ()) + return false; + + return CheckTypeName (type.GetElementType (), name.Substring (0, startIndex).TrimEnd ()); + } + + if (type.IsPointer) { + if (name.Length < 2 || name[name.Length - 1] != '*') + return false; + + return CheckTypeName (type.GetElementType (), name.Substring (0, name.Length - 1).TrimEnd ()); + } + + if (type.IsGenericType) { + int startIndex = name.IndexOf ('<'); + int endIndex = name.Length - 1; + + if (startIndex == -1 || name[endIndex] != '>') { + // the user-specified type is not a generic type + return false; + } + + // make sure that the type name matches (minus generics) + string subName = name.Substring (0, startIndex); + string typeName = type.FullName; + int tick; + + if ((tick = typeName.IndexOf ('`')) != -1) + typeName = typeName.Substring (0, tick); + + if (!CheckTypeName (typeName, subName)) + return false; + + string[] paramTypes; + if (!FunctionBreakpoint.TryParseParameters (name, startIndex + 1, endIndex, out paramTypes)) + return false; + + TypeMirror[] argTypes = type.GetGenericArguments (); + if (paramTypes.Length != argTypes.Length) + return false; + + for (int i = 0; i < paramTypes.Length; i++) { + if (!CheckTypeName (argTypes[i], paramTypes[i])) + return false; + } + } else if (!CheckTypeName (type.CSharpName, name)) { + if (!CheckTypeName (type.FullName, name)) + return false; + } + + return true; + } + + static bool CheckMethodParams (MethodMirror method, string[] paramTypes) + { + if (paramTypes == null) { + // User supplied no params to match against, match anything we find. + return true; + } + + var parameters = method.GetParameters (); + if (parameters.Length != paramTypes.Length) + return false; + + for (int i = 0; i < paramTypes.Length; i++) { + if (!CheckTypeName (parameters[i].ParameterType, paramTypes[i])) + return false; + } + + return true; + } + + bool IsGenericMethod (MethodMirror method) + { + return vm.Version.AtLeast (2, 12) && method.IsGenericMethod; + } + + //If paramType == null all overloads are returned + IEnumerable FindMethodsByName (string typeName, string methodName, string[] paramTypes) + { + if (!started) + yield break; + + if (vm.Version.AtLeast (2, 9)) { + // FIXME: need a way of querying all types so we can substring match typeName (e.g. user may have typed "Console" instead of "System.Console") + foreach (var type in vm.GetTypes (typeName, false)) { + ProcessType (type); + + foreach (var method in type.GetMethodsByNameFlags (methodName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static, true)) { + if (!CheckMethodParams (method, paramTypes)) + continue; + + yield return method; + } + } + } + + yield break; + } + + IList FindLocationsByFile (string file, int line, int column, out bool genericTypeOrMethod, out bool insideLoadedRange) + { + var locations = new List (); + + genericTypeOrMethod = false; + insideLoadedRange = false; + + if (!started) + return locations; + + var filename = Path.GetFileName (file); + + AddFileToSourceMapping (filename); + + lock (source_to_type) { + // Try already loaded types in the current source file + if (source_to_type.TryGetValue (filename, out var mirrors)) { + foreach (var type in mirrors) { + var location = FindLocationByType (type, file, line, column, out var genericMethod, out var insideRange); + if (insideRange) + insideLoadedRange = true; + + if (location != null) { + if (genericMethod || type.IsGenericType) + genericTypeOrMethod = true; + + locations.Add (location); + } + } + } + } + + return locations; + } + + private void AddFileToSourceMapping (string filename) + { + // + // Fetch types matching the source file from the debuggee, and add them + // to the source file->type mapping tables. + // This is needed because we don't receive type load events for all types, + // just the ones which match a source file with an existing breakpoint. + // + if (vm.Version.AtLeast (2, 9)) { + IList typesInFile; + if (vm.Version.AtLeast (2, 12)) { + typesInFile = vm.GetTypesForSourceFile (filename, IgnoreFilenameCase); + } else { + typesInFile = vm.GetTypesForSourceFile (filename, false); + //HACK: with older versions of sdb that don't support case-insenitive compares, + //explicitly try lowercased drivename on windows, since csc (when not hosted in VS) lowercases + //the drivename in the pdb files that get converted to mdbs as-is + if (typesInFile.Count == 0 && IsWindows) { + string alternateCaseFilename = char.ToLower (filename [0]) + filename.Substring (1); + typesInFile = vm.GetTypesForSourceFile (alternateCaseFilename, false); + } + } + + foreach (TypeMirror t in typesInFile) + ProcessType (t); + } + } + + public override bool CanCancelAsyncEvaluations + { + get + { + return Adaptor.IsEvaluating; + } + } + + protected override void OnCancelAsyncEvaluations () + { + Adaptor.CancelAsyncOperations (); + } + + protected override void OnNextInstruction () + { + Step (StepDepth.Over, StepSize.Min); + } + + protected override void OnNextLine () + { + Step (StepDepth.Over, StepSize.Line); + } + + void Step (StepDepth depth, StepSize size) + { + try { + Adaptor.CancelAsyncOperations (); // This call can block, so it has to run in background thread to avoid keeping the main session lock + var req = vm.CreateStepRequest (current_thread); + req.Depth = depth; + req.Size = size; + req.Filter = ShouldFilterStaticCtor () | StepFilter.DebuggerHidden | StepFilter.DebuggerStepThrough; + if (Options.ProjectAssembliesOnly) + req.Filter |= StepFilter.DebuggerNonUserCode; + if (assemblyFilters != null && assemblyFilters.Count > 0) + req.AssemblyFilter = assemblyFilters; + try { + req.Enabled = true; + } catch (NotSupportedException e) { + if (vm.Version.AtLeast (2, 19)) //catch NotSupportedException thrown by old version of protocol + throw e; + } + currentStepRequest = req; + OnResumed (); + vm.Resume (); + DequeueEventsForFirstThread (); + } catch (CommandException ex) { + string reason; + + switch (ex.ErrorCode) { + case ErrorCode.INVALID_FRAMEID: reason = "invalid frame id"; break; + case ErrorCode.NOT_SUSPENDED: reason = "VM not suspended"; break; + case ErrorCode.ERR_UNLOADED: reason = "AppDomain has been unloaded"; break; + case ErrorCode.NO_SEQ_POINT_AT_IL_OFFSET: reason = "no sequence point at the specified IL offset"; break; + default: reason = ex.ErrorCode.ToString (); break; + } + + if (outputOptions.ExceptionMessage) + OnDebuggerOutput (true, string.Format ("Step request failed: {0}.", reason)); + DebuggerLoggingService.LogError ("Step request failed", ex); + } catch (Exception ex) { + if (outputOptions.ExceptionMessage) + OnDebuggerOutput(true, string.Format ("Step request failed: {0}", ex.Message)); + DebuggerLoggingService.LogError ("Step request failed", ex); + } + } + + private StepFilter ShouldFilterStaticCtor() + { + var frames = current_thread.GetFrames(); + return (frames.Any(f => f.Method.Name == ".cctor" && f.Method.IsSpecialName && f.Method.IsStatic)) + ? StepFilter.None : StepFilter.StaticCtor; + } + + void EventHandler () + { + int? exit_code = null; + + while (true) { + try { + EventSet e = vm.GetNextEventSet (); + var type = e[0].EventType; + if (type == EventType.VMDeath || type == EventType.VMDisconnect) { + if (type == EventType.VMDeath && vm.Version.AtLeast (2, 27)) { + exit_code = ((VMDeathEvent) e[0]).ExitCode; + } + break; + } + HandleEventSet (e); + } catch (Exception ex) { + if (HasExited) + break; + + if (!HandleException (ex) && outputOptions.ExceptionMessage) + OnDebuggerOutput (true, ex.ToString ()); + + if (ex is VMDisconnectedException || ex is IOException || ex is SocketException) + break; + } + } + + try { + // This is a workaround for a mono bug + // Without this call, the process may become zombie in mono < 2.10.2 + if (vm.Process != null) + vm.Process.WaitForExit (1); + } catch (SystemException) { + } + + OnTargetEvent (new TargetEventArgs (TargetEventType.TargetExited) { + ExitCode = exit_code + }); + } + + protected override bool HandleException (Exception ex) + { + HideConnectionDialog (); + + if (HasExited) + return true; + + if (ex is VMDisconnectedException || ex is IOException) { + ex = new DisconnectedException (ex); + HasExited = true; + } else if (ex is SocketException) { + ex = new DebugSocketException (ex); + HasExited = true; + } + + return base.HandleException (ex); + } + + void HandleEventSet (EventSet es) + { + var type = es[0].EventType; + +#if DEBUG_EVENT_QUEUEING + if (type != TypeLoadEvent) + Console.WriteLine ("pp eventset({0}): {1}", es.Events.Length, es[0]); +#endif + + // If we are currently stopped on a thread, and the break events are on a different thread, we must queue + // that event set and dequeue it next time we resume. This eliminates race conditions when multiple threads + // hit breakpoints or catchpoints simultaneously. + // + bool isBreakEvent = type == EventType.Step || type == EventType.Breakpoint || type == EventType.Exception || type == EventType.UserBreak; + if (isBreakEvent) { + if (current_thread != null && es[0].Thread.Id != current_thread.Id) { + QueueBreakEventSet (es.Events); + } else { + HandleBreakEventSet (es.Events, false); + } + return; + } + + switch (type) { + case EventType.AssemblyLoad: + HandleAssemblyLoadEvents (Array.ConvertAll (es.Events, item => (AssemblyLoadEvent)item)); + break; + case EventType.AppDomainUnload: + HandleDomainUnloadEvents (Array.ConvertAll (es.Events, item => (AppDomainUnloadEvent)item)); + break; + case EventType.VMStart: + HandleVMStartEvents (Array.ConvertAll (es.Events, item => (VMStartEvent)item)); + break; + case EventType.TypeLoad: + HandleTypeLoadEvents (Array.ConvertAll (es.Events, item => (TypeLoadEvent)item)); + break; + case EventType.ThreadStart: + HandleThreadStartEvents (Array.ConvertAll (es.Events, item => (ThreadStartEvent)item)); + break; + case EventType.ThreadDeath: + HandleThreadDeathEvents (Array.ConvertAll (es.Events, item => (ThreadDeathEvent)item)); + break; + case EventType.UserLog: + HandleUserLogEvents (Array.ConvertAll (es.Events, item => (UserLogEvent)item)); + break; + case EventType.MethodUpdate: + HandleMethodUpdateEvents (Array.ConvertAll (es.Events, item => (MethodUpdateEvent)item)); + break; + default: + DebuggerLoggingService.LogMessage ("Ignoring unknown debugger event type {0}", type); + break; + } + + try { + if (!vm.Version.AtLeast (2, 55) || es.SuspendPolicy != SuspendPolicy.None) + vm.Resume (); + } catch (VMNotSuspendedException) { + if (type != EventType.VMStart && vm.Version.AtLeast (2, 2)) + throw; + } + } + + static bool IsStepIntoRequest (StepEventRequest stepRequest) + { + return stepRequest.Depth == StepDepth.Into; + } + + static bool IsStepOutRequest (StepEventRequest stepRequest) + { + return stepRequest.Depth == StepDepth.Out; + } + + static bool IsPropertyOrOperatorMethod (MethodMirror method) + { + string name = method.Name; + + return method.IsSpecialName && + (name.StartsWith ("get_", StringComparison.Ordinal) || + name.StartsWith ("set_", StringComparison.Ordinal) || + name.StartsWith ("op_", StringComparison.Ordinal)); + } + + static bool IsCompilerGenerated (MethodMirror method) + { + foreach (var attr in method.GetCustomAttributes(false)) { + if (attr.Constructor.DeclaringType.FullName == "System.Runtime.CompilerServices.CompilerGeneratedAttribute") + return true; + } + return false; + } + + bool IsUserAssembly (AssemblyMirror assembly) + { + if (userAssemblyNames == null) + return true; + + var name = assembly.GetName ().FullName; + + foreach (var n in userAssemblyNames) { + if (n == name) + return true; + } + + return false; + } + + bool IsAutoGeneratedFrameworkEnumerator (TypeMirror type) + { + if (IsUserAssembly (type.Assembly)) + return false; + + if (!SoftDebuggerAdaptor.IsGeneratedType (type)) + return false; + + foreach (var iface in type.GetInterfaces ()) { + if (iface.Namespace == "System.Collections" && iface.Name == "IEnumerator") + return true; + } + + return false; + } + + bool StepThrough (MethodMirror method) + { + if (Options.ProjectAssembliesOnly && !IsUserAssembly (method.DeclaringType.Assembly)) + return true; + + //With Sdb 2.30 this logic was moved to Runtime no need to spend time on checking this + if (!vm.Version.AtLeast (2, 30)) { + if (vm.Version.AtLeast (2, 21)) { + foreach (var attr in method.GetCustomAttributes (false)) { + var attrName = attr.Constructor.DeclaringType.FullName; + + switch (attrName) { + case "System.Diagnostics.DebuggerHiddenAttribute": + return true; + case "System.Diagnostics.DebuggerStepThroughAttribute": + return true; + case "System.Diagnostics.DebuggerNonUserCodeAttribute": + return Options.ProjectAssembliesOnly; + } + } + } + + if (Options.ProjectAssembliesOnly) { + foreach (var attr in method.DeclaringType.GetCustomAttributes (false)) { + var attrName = attr.Constructor.DeclaringType.FullName; + + if (attrName == "System.Diagnostics.DebuggerNonUserCodeAttribute") + return Options.ProjectAssembliesOnly; + } + } + } + + return false; + } + + bool ContinueOnStepInto (MethodMirror method) + { + if (vm.Version.AtLeast (2, 21)) { + foreach (var attr in method.GetCustomAttributes (false)) { + var attrName = attr.Constructor.DeclaringType.FullName; + + if (attrName == "System.Diagnostics.DebuggerStepperBoundaryAttribute") + return true; + } + } + + return false; + } + + bool IgnoreBreakpoint (MethodMirror method) + { + if (vm.Version.AtLeast (2, 21)) { + foreach (var attr in method.GetCustomAttributes (false)) { + var attrName = attr.Constructor.DeclaringType.FullName; + + switch (attrName) { + case "System.Diagnostics.DebuggerHiddenAttribute": return true; + case "System.Diagnostics.DebuggerStepThroughAttribute": return true; + case "System.Diagnostics.DebuggerNonUserCodeAttribute": return Options.ProjectAssembliesOnly; + case "System.Diagnostics.DebuggerStepperBoundaryAttribute": return true; + } + } + } + + if (Options.ProjectAssembliesOnly) { + foreach (var attr in method.DeclaringType.GetCustomAttributes (false)) { + var attrName = attr.Constructor.DeclaringType.FullName; + + if (attrName == "System.Diagnostics.DebuggerNonUserCodeAttribute") + return Options.ProjectAssembliesOnly; + } + } + + return false; + } + + /// + /// Checks all frames in thread where exception occured and if any frame has user code it returns true. + /// Also notice that this method already check if Options.ProjectAssembliesOnly==false + /// + bool ExceptionInUserCode (ExceptionEvent ev) + { + // this is just optimization to prevent need to fetch Frames + if (Options.ProjectAssembliesOnly == false) + return true; + foreach (var frame in ev.Thread.GetFrames ()) { + if (!IsExternalCode (frame)) + return true; + } + return false; + } + + bool ShouldIgnore (ExceptionEvent ev) + { + BreakInfo binfo; + if (!breakpoints.TryGetValue (ev.Request, out binfo)) + return false; + + var cp = binfo.BreakEvent as Catchpoint; + if (cp == null) + return false; + + var backtrace = GetThreadBacktrace (ev.Thread); + if (backtrace.FrameCount == 0) { + return cp.ShouldIgnore (ev.Exception.Type.FullName, null); + } else { + var frame = backtrace.GetFrame (0); + return cp.ShouldIgnore (ev.Exception.Type.FullName, frame.GetLocationSignature ()); + } + } + + void HandleBreakEventSet (Event[] es, bool dequeuing) + { + if (dequeuing && HasExited) + return; + + TargetEventType etype = TargetEventType.TargetStopped; + ObjectMirror exception = null; + BreakEvent breakEvent = null; + bool redoCurrentStep = false; + bool steppedInto = false; + bool steppedOut = false; + bool resume = true; + BreakInfo binfo; + + if (es [0].EventType == EventType.Exception) { + var bad = es.FirstOrDefault (ee => ee.EventType != EventType.Exception); + if (bad != null) + throw new Exception ("Catchpoint eventset had unexpected event type " + bad.GetType ()); + var ev = (ExceptionEvent)es [0]; + exception = ev.Exception; + if (ev.Request == unhandledExceptionRequest) { + etype = TargetEventType.UnhandledException; + if (exception.Type.FullName != "System.Threading.ThreadAbortException") + resume = false; + } else { + // Set the exception for this thread so that CatchPoint Print message(tracing) of {$exception} works + activeExceptionsByThread [es[0].Thread.ThreadId] = exception; + if (ExceptionInUserCode(ev) && !ShouldIgnore(ev) && !HandleBreakpoint (es [0].Thread, ev.Request)) { + etype = TargetEventType.ExceptionThrown; + resume = false; + } + + // Remove exception from the thread so that when the program stops due to stepFinished/programPause/breakPoint... + // we don't have on out-dated exception(setting and unsetting few lines later is needed because it's used inside HandleBreakpoint) + activeExceptionsByThread.Remove (es[0].Thread.ThreadId); + + // Get the breakEvent so that we can check if we should ignore it later + if (breakpoints.TryGetValue (ev.Request, out binfo)) + breakEvent = binfo.BreakEvent; + } + } else { + //always need to evaluate all breakpoints, some might be tracepoints or conditional bps with counters + foreach (Event e in es) { + if (e.EventType == EventType.Breakpoint) { + var be = (BreakpointEvent) e; + var hasBreakInfo = breakpoints.TryGetValue (be.Request, out binfo); + + if (!HandleBreakpoint (e.Thread, be.Request)) { + etype = TargetEventType.TargetHitBreakpoint; + autoStepInto = false; + resume = false; + if (hasBreakInfo) + breakEvent = binfo.BreakEvent; + } + + if (hasBreakInfo) { + if (currentStepRequest != null && + currentStepRequest.Depth != StepDepth.Out && + binfo.Location.ILOffset == currentAddress && + e.Thread.Id == currentStepRequest.Thread.Id && + currentStackDepth == e.Thread.GetFrames ().Length) + redoCurrentStep = true; + } + } else if (e.EventType == EventType.Step) { + var stepRequest = e.Request as StepEventRequest; + steppedInto = IsStepIntoRequest (stepRequest); + steppedOut = IsStepOutRequest (stepRequest); + etype = TargetEventType.TargetStopped; + resume = false; + } else if (e.EventType == EventType.UserBreak) { + etype = TargetEventType.TargetStopped; + autoStepInto = false; + resume = false; + } else { + throw new Exception ("Break eventset had unexpected event type " + e.GetType ()); + } + } + } + + if (redoCurrentStep) { + StepDepth depth = currentStepRequest.Depth; + StepSize size = currentStepRequest.Size; + + current_thread = recent_thread = es[0].Thread; + currentStepRequest.Enabled = false; + currentStepRequest = null; + + Step (depth, size); + } else if (resume) { + // all breakpoints were conditional and evaluated as false + current_thread = null; + vm.Resume (); + DequeueEventsForFirstThread (); + } else { + if (currentStepRequest != null) { + currentStepRequest.Enabled = false; + currentStepRequest = null; + } + + current_thread = recent_thread = es[0].Thread; + + if (exception != null) + activeExceptionsByThread [current_thread.ThreadId] = exception; + + var backtrace = GetThreadBacktrace (current_thread); + bool stepInto = false; + bool stepOut = false; + + if (backtrace.FrameCount > 0) { + var frame = backtrace.GetFrame (0) as SoftDebuggerStackFrame; + currentAddress = frame != null ? frame.Address : -1; + currentStackDepth = backtrace.FrameCount; + + if (frame != null && steppedInto) { + if (ContinueOnStepInto (frame.StackFrame.Method)) { + current_thread = null; + vm.Resume (); + DequeueEventsForFirstThread (); + return; + } + + if (StepThrough (frame.StackFrame.Method)) { + // The method has a Debugger[Hidden,StepThrough,NonUserCode]Attribute on it + // Keep calling StepInto until we land somewhere without one of these attributes + stepInto = true; + } else if (frame.StackFrame.ILOffset == 0 && IsPropertyOrOperatorMethod (frame.StackFrame.Method) && + (Options.StepOverPropertiesAndOperators || IsCompilerGenerated (frame.StackFrame.Method))) { + //We want to skip property only when we just stepped into property(ILOffset==0) + //so if user puts breakpoint inside property we don't want to StepOut for him when he steps after breakpoint is hit + + //mcs.exe and Roslyn are also emmiting Sequence point inside auto-properties so breakpoint can be placed + //we want to always skip auto-properties also when StepOverProperties is disabled hence "|| IsCompilerGenerated" + + // We will want to call StepInto once StepOut returns... + autoStepInto = true; + stepOut = true; + } else if (IsAutoGeneratedFrameworkEnumerator (frame.StackFrame.Method.DeclaringType)) { + // User asked to step in, but we landed in an autogenerated type (probably an iterator) + autoStepInto = true; + stepOut = true; + } + } else if (etype == TargetEventType.TargetHitBreakpoint && breakEvent != null && !breakEvent.NonUserBreakpoint && IgnoreBreakpoint (frame.StackFrame.Method)) { + current_thread = null; + vm.Resume (); + DequeueEventsForFirstThread (); + return; + } + } + + if (stepOut) { + Step (StepDepth.Out, StepSize.Min); + } else if (stepInto) { + Step (StepDepth.Into, StepSize.Min); + } else if (steppedOut && autoStepInto) { + autoStepInto = false; + Step (StepDepth.Into, StepSize.Min); + } else { + var args = new TargetEventArgs (etype); + args.Process = OnGetProcesses () [0]; + args.Thread = GetThread (args.Process, current_thread); + args.Backtrace = backtrace; + args.BreakEvent = breakEvent; + + OnTargetEvent (args); + } + } + } + + public void AddUserAssembly(string userAssembly) + { + if (userAssemblyNames != null && !userAssemblyNames.Contains(userAssembly)) + userAssemblyNames.Add(userAssembly); + } + + void HandleAssemblyLoadEvents (AssemblyLoadEvent[] events) + { + var asm = events [0].Assembly; + if (events.Length > 1 && events.Any (a => a.Assembly != asm)) + throw new InvalidOperationException ("Simultaneous AssemblyLoadEvent for multiple assemblies"); + + HandleAssemblyLoaded (asm); + RegisterAssembly (asm); + bool isExternal; + isExternal = !UpdateAssemblyFilters (asm) && userAssemblyNames != null; + + if (outputOptions.ModuleLoaded) { + string flagExt = isExternal ? " [External]" : ""; + OnDebuggerOutput (false, string.Format ("Loaded assembly: {0}{1}\n", asm.Location, flagExt)); + } + } + + private void HandleAssemblyLoaded (AssemblyMirror asm) + { + var name = asm.GetName (); + var assemblyObject = asm.GetAssemblyObject (); + bool isDynamic = asm.IsDynamic; + string assemblyName; + bool hasSymbol; + if (!isDynamic) { + assemblyName = name.Name; + hasSymbol = asm.HasDebugInfo; + } else { + assemblyName = string.Empty; + hasSymbol = false; + } + var assembly = new Assembly ( + assemblyName, + asm?.Location ?? string.Empty, + true, + hasSymbol, + string.Empty, + string.Empty, + -1, + name?.Version?.Major.ToString () ?? string.Empty, + // TODO: module time stamp + string.Empty, + assemblyObject?.Address.ToString () ?? string.Empty, + string.Format ("[{0}]{1}", asm?.VirtualMachine?.TargetProcess?.Id ?? -1, asm?.VirtualMachine?.TargetProcess?.ProcessName ?? string.Empty), + asm?.Domain?.FriendlyName ?? string.Empty, + asm?.VirtualMachine?.TargetProcess?.Id ?? -1, + hasSymbol, + isDynamic + ); + + OnAssemblyLoaded (assembly); + } + + void RegisterAssembly (AssemblyMirror asm) + { + var domain = vm.Version.AtLeast (2, 45) ? asm.Domain : asm.GetAssemblyObject ().Domain; + if (domainAssembliesToUnload.TryGetValue (domain, out var asmList)) { + asmList.Add (asm); + } else { + domainAssembliesToUnload.Add (domain, new HashSet (new [] { asm })); + } + } + + void HandleDomainUnloadEvents (AppDomainUnloadEvent [] events) + { + var domain = events [0].Domain; + if (events.Length > 1 && events.Any (a => a.Domain != domain)) + throw new InvalidOperationException ("Simultaneous DomainUnloadEvents for multiple domains"); + if (domainAssembliesToUnload.TryGetValue (domain, out var asmList)) { + foreach (var asm in asmList) { + HandleAssemblyUnloadEvents (asm); + } + } + } + + void HandleAssemblyUnloadEvents (AssemblyMirror asm) + { + assemblyFilters?.Remove (asm); + + // Mark affected breakpoints as pending again + var affectedBreakpoints = new List> (breakpoints.Where (x => x.Value.Requests.TryGetValue (x.Key, out var a) && a == asm)); + foreach (var breakpoint in affectedBreakpoints) { + var affectedRequests = breakpoint.Value.Requests.Where (r => r.Value == asm).ToArray (); + foreach (var item in affectedRequests) { + breakpoint.Value.Requests.Remove (item.Key); + breakpoints.Remove (breakpoint.Key); + } + if (!breakpoint.Value.Requests.Any ()) { + string file = breakpoint.Value.Location.SourceFile; + int line = breakpoint.Value.Location.LineNumber; + OnDebuggerOutput (false, string.Format ("Re-pending breakpoint at {0}:{1}\n", file, line)); + breakpoint.Value.SetStatus (BreakEventStatus.NotBound, "Assembly unloaded"); + } + } + + // Remove affected types from the loaded types list + var affectedTypes = types.SelectMany (l => l.Value).Where (t => t.Assembly == asm).ToArray (); + + foreach (var tm in affectedTypes) { + List typesList; + if (tm.IsNested) { + if (aliases.TryGetValue (NestedTypeNameToAlias (tm.FullName), out typesList)) { + typesList.Remove (tm); + if (typesList.Count == 0) + aliases.Remove (NestedTypeNameToAlias (tm.FullName)); + } + } + if (types.TryGetValue (tm.FullName, out typesList)) { + typesList.Remove (tm); + if (typesList.Count == 0) + types.Remove (tm.FullName); + } + } + + lock (source_to_type) { + foreach (var pair in source_to_type) + pair.Value.RemoveAll (m => m.Assembly == asm); + } + + if (outputOptions.ModuleUnoaded) + OnDebuggerOutput (false, string.Format ("Unloaded assembly: {0}\n", GetAssemblyLocation (asm) ?? "")); + } + + static string GetAssemblyLocation (AssemblyMirror asm) + { + if (asm == null) + return null; + try { + return asm.Location; + } catch (CommandException ex) { + if (ex.ErrorCode != ErrorCode.ERR_UNLOADED) + throw ex; + return null; + } + } + + void HandleVMStartEvents (VMStartEvent[] events) + { + var thread = events [0].Thread; + if (events.Length > 1) + throw new InvalidOperationException ("Simultaneous VMStartEvents"); + + OnStarted (new Mono.Debugging.Client.ThreadInfo (0, GetId (thread), GetThreadName (thread), null)); + //HACK: 2.6.1 VM doesn't emit type load event, so work around it + var t = vm.RootDomain.Corlib.GetType ("System.Exception", false, false); + if (t != null) { + ResolveBreakpoints (t); + } + } + + void HandleTypeLoadEvents (TypeLoadEvent[] events) + { + var type = events [0].Type; + if (events.Length > 1 && events.Any (a => a.Type != type)) + throw new InvalidOperationException ("Simultaneous TypeLoadEvents for multiple types"); + + List typesList; + if (!(types.TryGetValue (type.FullName, out typesList) && typesList.Contains (type))) + ResolveBreakpoints (type); + } + + void HandleThreadStartEvents (ThreadStartEvent[] events) + { + current_threads = null; + var thread = events [0].Thread; + if (events.Length > 1 && events.Any (a => a.Thread != thread)) + throw new InvalidOperationException ("Simultaneous ThreadStartEvents for multiple threads"); + + var name = GetThreadName (thread); + var id = GetId (thread); + OnDebuggerOutput (false, string.Format ("Thread started: {0} #{1}\n", name, id)); + OnTargetEvent (new TargetEventArgs (TargetEventType.ThreadStarted) { + Thread = new Mono.Debugging.Client.ThreadInfo (0, id, name, null), + }); + } + + void HandleThreadDeathEvents (ThreadDeathEvent[] events) + { + current_threads = null; + ThreadMirror thread; + try { + thread = events [0].Thread; + } catch (ObjectCollectedException) { + // A 2.1 debugger-agent can send a ThreadDeath event for a ThreadMirror + // already collected, in that case just allow the session to continue + if (!vm.Version.AtLeast (2, 2)) + return; + + // Otherwise just report the error + throw; + } + if (events.Length > 1 && events.Any (a => a.Thread != thread)) + throw new InvalidOperationException ("Simultaneous ThreadDeathEvents for multiple threads"); + + var name = GetThreadName (thread); + var id = GetId (thread); + if (outputOptions.ThreadExited) + OnDebuggerOutput (false, string.Format ("Thread finished: {0} #{1}\n", name, id)); + OnTargetEvent (new TargetEventArgs (TargetEventType.ThreadStopped) { + Thread = new Mono.Debugging.Client.ThreadInfo (0, id, name, null), + }); + } + + void HandleUserLogEvents (UserLogEvent[] events) + { + foreach (var ul in events) + OnTargetDebug (ul.Level, ul.Category, ul.Message); + } + + void HandleMethodUpdateEvents(MethodUpdateEvent[] methods) + { + foreach (var method in methods) //add new methods to type + { + method.GetMethod ().ClearCachedLocalsDebugInfo (); + method.GetMethod ().DeclaringType.AddMethodIfNotExist (method.GetMethod ()); + } + foreach (var breakpoint in breakpoints) { + Breakpoint bp = ((Breakpoint)breakpoint.Value.BreakEvent); + if (bp.UpdatedByEnC) { + bool dummy = false; + var l = FindLocationByMethod (breakpoint.Value.Location.Method, bp.FileName, bp.Line, bp.Column, ref dummy); + if (l != null) { + breakpoint.Value.Location = l; + UpdateBreakpoint (bp, breakpoint.Value); + bp.UpdatedByEnC = false; + } + } + } + foreach (var bp in pending_bes) { + if (bp.Status != BreakEventStatus.Bound) { + foreach (var location in FindLocationsByFile (bp.Breakpoint.FileName, bp.Breakpoint.Line, bp.Breakpoint.Column, out _, out bool insideLoadedRange)) { + OnDebuggerOutput (false, string.Format ("Resolved pending breakpoint at '{0}:{1},{2}' to {3} [0x{4:x5}].\n", + bp.Breakpoint.FileName, bp.Breakpoint.Line, bp.Breakpoint.Column, + GetPrettyMethodName (location.Method), location.ILOffset)); + + bp.Location = location; + InsertBreakpoint (bp.Breakpoint, bp); + bp.SetStatus (BreakEventStatus.Bound, null); + } + } + } + } + + public ObjectMirror GetExceptionObject (ThreadMirror thread) + { + ObjectMirror obj; + + return activeExceptionsByThread.TryGetValue (thread.ThreadId, out obj) ? obj : null; + } + + void QueueBreakEventSet (Event[] eventSet) + { +#if DEBUG_EVENT_QUEUEING + Console.WriteLine ("qq eventset({0}): {1}", eventSet.Length, eventSet[0]); +#endif + var events = new List (eventSet); + lock (queuedEventSets) { + queuedEventSets.AddLast (events); + } + } + + void RemoveQueuedBreakEvents (Dictionary requests) + { + int resume = 0; + + lock (queuedEventSets) { + var node = queuedEventSets.First; + + while (node != null) { + List q = node.Value; + + for (int i = 0; i < q.Count; i++) { + foreach (var request in requests.Keys) { + if (q[i].Request == request) { + q.RemoveAt (i--); + break; + } + } + } + + if (q.Count == 0) { + var d = node; + node = node.Next; + queuedEventSets.Remove (d); + resume++; + } else { + node = node.Next; + } + } + } + + for (int i = 0; i < resume; i++) + vm.Resume (); + } + + void DequeueEventsForFirstThread () + { + List> dequeuing; + lock (queuedEventSets) { + if (queuedEventSets.Count < 1) + return; + + dequeuing = new List> (); + var node = queuedEventSets.First; + + //making this the current thread means that all events from other threads will get queued + current_thread = node.Value[0].Thread; + while (node != null) { + if (node.Value[0].Thread.Id == current_thread.Id) { + var d = node; + node = node.Next; + dequeuing.Add (d.Value); + queuedEventSets.Remove (d); + } else { + node = node.Next; + } + } + } + +#if DEBUG_EVENT_QUEUEING + foreach (var e in dequeuing) + Console.WriteLine ("dq eventset({0}): {1}", e.Count, e[0]); +#endif + + //firing this off in a thread prevents possible infinite recursion + ThreadPool.QueueUserWorkItem (delegate { + if (!HasExited) { + foreach (var es in dequeuing) { + try { + HandleBreakEventSet (es.ToArray (), true); + } catch (Exception ex) { + if (!HandleException (ex) && outputOptions.ExceptionMessage) + OnDebuggerOutput (true, ex.ToString ()); + + if (ex is VMDisconnectedException || ex is IOException || ex is SocketException) { + OnTargetEvent (new TargetEventArgs (TargetEventType.TargetExited)); + break; + } + } + } + } + }); + } + + bool HandleBreakpoint (ThreadMirror thread, EventRequest er) + { + BreakInfo binfo; + if (!breakpoints.TryGetValue (er, out binfo)) + return false; + + var bp = binfo.BreakEvent; + if (bp == null) + return false; + + binfo.IncrementHitCount (); + if (!binfo.HitCountReached) + return true; + + if (!string.IsNullOrEmpty (bp.ConditionExpression)) { + string res = EvaluateExpression (thread, bp.ConditionExpression, bp); + if (bp.BreakIfConditionChanges) { + if (res == binfo.LastConditionValue) + return true; + binfo.LastConditionValue = res; + } else { + if (res == null || res.ToLowerInvariant () != "true") + return true; + } + } + if ((bp.HitAction & HitAction.CustomAction) != HitAction.None) { + if (HandleProcessStartHook (thread, bp)) + return true; + // If custom action returns true, execution must continue + return binfo.RunCustomBreakpointAction (bp.CustomActionId); + } + + if ((bp.HitAction & HitAction.PrintTrace) != HitAction.None) { + OnTargetDebug (0, "", "Breakpoint reached: " + binfo.FileName + ":" + binfo.Location.LineNumber + Environment.NewLine); + } + + if ((bp.HitAction & HitAction.PrintExpression) != HitAction.None) { + string exp = EvaluateTrace (thread, bp.TraceExpression); + binfo.UpdateLastTraceValue (exp); + } + + // Continue execution if we don't have break action. + return (bp.HitAction & HitAction.Break) == HitAction.None; + } + + string EvaluateTrace (ThreadMirror thread, string exp) + { + var sb = new StringBuilder (); + int last = 0; + int i = exp.IndexOf ('{'); + while (i != -1) { + if (i < exp.Length - 1 && exp [i+1] == '{') { + sb.Append (exp, last, i - last + 1); + last = i + 2; + i = exp.IndexOf ('{', i + 2); + continue; + } + int j = exp.IndexOf ('}', i + 1); + if (j == -1) + break; + string se = exp.Substring (i + 1, j - i - 1); + se = EvaluateExpression (thread, se, null); + sb.Append (exp, last, i - last); + sb.Append (se); + last = j + 1; + i = exp.IndexOf ('{', last); + } + sb.Append (exp, last, exp.Length - last); + return sb.ToString (); + } + + static SourceLocation GetSourceLocation (StackFrame frame) + { + return new SourceLocation (frame.Method.Name, frame.FileName, frame.LineNumber, frame.ColumnNumber, frame.EndLineNumber, frame.EndColumnNumber); + } + + static string FormatSourceLocation (BreakEvent breakEvent) + { + var bp = breakEvent as Breakpoint; + if (bp == null || string.IsNullOrEmpty (bp.FileName)) + return null; + + var location = Path.GetFileName (bp.FileName); + if (bp.OriginalLine > 0) { + location += ":" + bp.OriginalLine; + if (bp.OriginalColumn > 0) + location += "," + bp.OriginalColumn; + } + + return location; + } + + static bool IsBoolean (ValueReference vr) + { + if (vr.Type is Type && ((Type) vr.Type) == typeof (bool)) + return true; + + if (vr.Type is TypeMirror && ((TypeMirror) vr.Type).FullName == "System.Boolean") + return true; + + return false; + } + + string EvaluateExpression (ThreadMirror thread, string expression, BreakEvent bp) + { + try { + var frames = thread.GetFrames (); + if (frames.Length == 0) + return string.Empty; + + EvaluationOptions ops = Options.EvaluationOptions.Clone (); + ops.AllowTargetInvoke = true; + ops.EllipsizedLength = 1000; + + var ctx = new SoftEvaluationContext (this, frames[0], ops); + + if (bp != null) { + // validate conditional breakpoint expressions so that we can provide error reporting to the user + var vr = ctx.Evaluator.ValidateExpression (ctx, expression); + if (!vr.IsValid) { + string message = string.Format ("Invalid expression in conditional breakpoint. {0}", vr.Message); + string location = FormatSourceLocation (bp); + + if (!string.IsNullOrEmpty (location)) + message = location + ": " + message; + + OnDebuggerOutput (true, message); + return string.Empty; + } + + // resolve types... + if (ctx.SourceCodeAvailable) + expression = ctx.Evaluator.Resolve (this, GetSourceLocation (frames[0]), expression); + } + + ValueReference val = ctx.Evaluator.Evaluate (ctx, expression); + if (bp != null && !bp.BreakIfConditionChanges && !IsBoolean (val)) { + string message = string.Format ("Expression in conditional breakpoint did not evaluate to a boolean value: {0}", bp.ConditionExpression); + string location = FormatSourceLocation (bp); + + if (!string.IsNullOrEmpty (location)) + message = location + ": " + message; + + OnDebuggerOutput (true, message); + return string.Empty; + } + + return val.CreateObjectValue (false).Value; + } catch (EvaluatorException ex) { + string message; + + if (bp != null) { + message = string.Format ("Failed to evaluate expression in conditional breakpoint. {0}", ex.Message); + string location = FormatSourceLocation (bp); + + if (!string.IsNullOrEmpty (location)) + message = location + ": " + message; + } else { + message = ex.ToString (); + } + + OnDebuggerOutput (true, message); + return string.Empty; + } catch (Exception ex) { + OnDebuggerOutput (true, ex.ToString ()); + return string.Empty; + } + } + + static string NestedTypeNameToAlias (string typeName) + { + int index = typeName.IndexOfAny (new [] { '[', ',' }); + + if (index == -1) + return typeName.Replace ('+', '.'); + + var prefix = typeName.Substring (0, index).Replace ('+', '.'); + var suffix = typeName.Substring (index); + + return prefix + suffix; + } + + void ProcessType (TypeMirror t) + { + string typeName = t.FullName; + + List typesList; + if (types.TryGetValue (typeName, out typesList) && typesList.Contains (t)) + return; + + RegisterAssembly (t.Assembly); + + if (t.IsNested) { + var alias = NestedTypeNameToAlias (typeName); + List aliasesList; + if (aliases.TryGetValue (alias, out aliasesList)) { + aliasesList.Add (t); + } else { + aliases [alias] = new List (new [] { t }); + } + } + types [typeName] = typesList ?? new List (); + types [typeName].Add (t); + + //get the source file paths + //full paths, from GetSourceFiles (true), are only supported by sdb protocol 2.2 and later + string[] sourceFiles; + if (vm.Version.AtLeast (2, 2)) { + sourceFiles = t.GetSourceFiles ().Select ((fullPath) => Path.GetFileName (fullPath)).ToArray (); + } else { + sourceFiles = t.GetSourceFiles (); + + //HACK: if mdb paths are windows paths but the sdb agent is on unix, it won't map paths to filenames correctly + if (IsWindows) { + for (int i = 0; i < sourceFiles.Length; i++) { + string s = sourceFiles[i]; + if (s != null && !s.StartsWith ("/", StringComparison.Ordinal)) + sourceFiles[i] = Path.GetFileName (s); + } + } + } + + for (int n = 0; n < sourceFiles.Length; n++) + sourceFiles[n] = NormalizePath (sourceFiles[n]); + + foreach (string s in sourceFiles) { + lock (source_to_type) { + if (source_to_type.TryGetValue (s, out typesList)) { + typesList.Add (t); + } else { + typesList = new List (); + typesList.Add (t); + source_to_type[s] = typesList; + } + } + } + + type_to_source [t] = sourceFiles; + } + + static string[] GetParamTypes (MethodMirror method) + { + var paramTypes = new List (); + + foreach (var param in method.GetParameters ()) + paramTypes.Add (param.ParameterType.CSharpName); + + return paramTypes.ToArray (); + } + + string GetPrettyMethodName (MethodMirror method) + { + var name = new StringBuilder (); + + name.Append (Adaptor.GetDisplayTypeName (method.ReturnType.FullName)); + name.Append (" "); + name.Append (Adaptor.GetDisplayTypeName (method.DeclaringType.FullName)); + name.Append ("."); + name.Append (method.Name); + + if (method.VirtualMachine.Version.AtLeast (2, 12)) { + if (method.IsGenericMethodDefinition || method.IsGenericMethod) { + name.Append ("<"); + if (method.VirtualMachine.Version.AtLeast (2, 15)) { + var argTypes = method.GetGenericArguments (); + for (int i = 0; i < argTypes.Length; i++) { + if (i != 0) + name.Append (", "); + name.Append (Adaptor.GetDisplayTypeName (argTypes[i].FullName)); + } + } + name.Append (">"); + } + } + + name.Append (" ("); + var @params = method.GetParameters (); + for (int i = 0; i < @params.Length; i++) { + if (i != 0) + name.Append (", "); + if (@params[i].Attributes.HasFlag (ParameterAttributes.Out)) { + if (@params[i].Attributes.HasFlag (ParameterAttributes.In)) + name.Append ("ref "); + else + name.Append ("out "); + } + name.Append (Adaptor.GetDisplayTypeName (@params[i].ParameterType.FullName)); + name.Append (" "); + name.Append (@params[i].Name); + } + name.Append (")"); + + return name.ToString (); + } + + void ResolveBreakpoints (TypeMirror type) + { + Location loc; + + ProcessType (type); + + // First, resolve FunctionBreakpoints + BreakInfo [] tempPendingBes; + lock (pending_bes) { + tempPendingBes = pending_bes.Where (b => b.BreakEvent is FunctionBreakpoint).ToArray (); + } + foreach (var bi in tempPendingBes) { + if (CheckTypeName (type, bi.TypeName)) { + var bp = (FunctionBreakpoint)bi.BreakEvent; + foreach (var method in FindMethodsByName (bp.TypeName, bp.MethodName, bp.ParamTypes)) { + ResolveFunctionBreakpoint (bi, bp, method); + } + } + } + + // Now resolve normal Breakpoints + foreach (string s in type_to_source [type]) { + lock (pending_bes) { + tempPendingBes = pending_bes.Where (b => (b.BreakEvent is Breakpoint) && !(b.BreakEvent is FunctionBreakpoint)).ToArray (); + } + foreach (var bi in tempPendingBes) { + var bp = (Breakpoint) bi.BreakEvent; + if (PathComparer.Compare (Path.GetFileName (bp.FileName), s) == 0) { + bool insideLoadedRange; + bool genericMethod; + + if (bi.BreakEvent is InstructionBreakpoint) { + loc = FindLocationByILOffset ((InstructionBreakpoint)bi.BreakEvent, bp.FileName, out genericMethod, out insideLoadedRange); + } else { + loc = FindLocationByType (type, bp.FileName, bp.Line, bp.Column, out genericMethod, out insideLoadedRange); + } + if (loc != null) { + OnDebuggerOutput (false, string.Format ("Resolved pending breakpoint at '{0}:{1},{2}' to {3} [0x{4:x5}].\n", + s, bp.Line, bp.Column, GetPrettyMethodName (loc.Method), loc.ILOffset)); + ResolvePendingBreakpoint (bi, loc); + } else { + if (insideLoadedRange) { + bi.SetStatus (BreakEventStatus.Invalid, null); + } + } + } + } + } + + // Thirdly, resolve pending catchpoints + lock (pending_bes) { + tempPendingBes = pending_bes.Where (b => b.BreakEvent is Catchpoint).ToArray (); + } + foreach (var bi in tempPendingBes) { + var cp = (Catchpoint) bi.BreakEvent; + if (cp.ExceptionName == type.FullName) { + ResolvePendingCatchpoint (bi, type); + } + } + } + + bool ResolveFunctionBreakpoint (BreakInfo bi, FunctionBreakpoint bp, MethodMirror method) + { + var loc = method.Locations.FirstOrDefault (); + string paramList = "(" + string.Join (", ", bp.ParamTypes ?? GetParamTypes (method)) + ")"; + if (loc != null) { + bi.Location = loc; + InsertBreakpoint (bp, bi); + OnDebuggerOutput (false, string.Format ("Resolved pending breakpoint for '{0}{1}' to {2}:{3} [0x{4:x5}].\n", + bp.FunctionName, paramList, loc.SourceFile, loc.LineNumber, loc.ILOffset)); + } else { + InsertBreakpoint (bp, bi, method, 0); + OnDebuggerOutput (false, string.Format ("Resolved pending breakpoint for '{0}{1}' to [0x0](no debug symbols).\n", + bp.FunctionName, paramList)); + } + bi.SetStatus (BreakEventStatus.Bound, null); + // Note: if the type or method is generic, there may be more instances so don't assume we are done resolving the breakpoint + return bp.ParamTypes != null && !method.DeclaringType.IsGenericType && !IsGenericMethod (method); + } + + internal static string NormalizePath (string path) + { + if (!IsWindows && (path.StartsWith ("\\", StringComparison.Ordinal) || (path.Length > 3 && path [1] == ':' && path [2] == '\\'))) + return path.Replace ('\\', '/'); + + return path; + } + + [DllImport ("libc")] + static extern IntPtr realpath (string path, IntPtr buffer); + + static string ResolveFullPath (string path) + { + if (IsWindows) + return Path.GetFullPath (path); + + const int PATHMAX = 4096 + 1; + IntPtr buffer = IntPtr.Zero; + + try { + buffer = Marshal.AllocHGlobal (PATHMAX); + var result = realpath (path, buffer); + var realPath = result == IntPtr.Zero ? "" : Marshal.PtrToStringAuto (buffer); + + if (string.IsNullOrEmpty(realPath) && !File.Exists(path)) { + // if the file does not exist then `realpath` will return empty string + // default to what we would do if calling this on windows + realPath = Path.GetFullPath (path); + } + + return realPath; + } finally { + if (buffer != IntPtr.Zero) + Marshal.FreeHGlobal (buffer); + } + } + + static string ResolveSymbolicLink (string path) + { + if (path.Length == 0) + return path; + + if (IsWindows) + return ResolveWindowsSymbolicLink (path); + + return ResolveUnixSymbolicLink (path); + } + + static string ResolveWindowsSymbolicLink (string path) + { + return Path.GetFullPath (path); + } + + static string ResolveUnixSymbolicLink (string path) + { + /*try { + var alreadyVisted = new HashSet (); + + while (true) { + if (alreadyVisted.Contains (path)) + return string.Empty; + + alreadyVisted.Add (path); + + var linkInfo = new Mono.Unix.UnixSymbolicLinkInfo (path); + if (linkInfo.IsSymbolicLink && linkInfo.HasContents) { + string contentsPath = linkInfo.ContentsPath; + + if (!Path.IsPathRooted (contentsPath)) + path = Path.Combine (Path.GetDirectoryName (path), contentsPath); + else + path = contentsPath; + + path = ResolveFullPath (path); + continue; + } + + path = Path.Combine (ResolveSymbolicLink (Path.GetDirectoryName (path)), Path.GetFileName (path)); + + return ResolveFullPath (path); + } + } catch { + return path; + }*/ + return path; + } + + static bool PathsAreEqual (string p1, string p2) + { + if (string.IsNullOrWhiteSpace (p1) || string.IsNullOrWhiteSpace (p2)) + return false; + + if (PathComparer.Compare (p1, p2) == 0) + return true; + + var rp1 = ResolveSymbolicLink (p1); + var rp2 = ResolveSymbolicLink (p2); + + return PathComparer.Compare (rp1, rp2) == 0; + } + + Location FindLocationByMethod (MethodMirror method, string file, int line, int column, ref bool insideTypeRange) + { + int rangeFirstLine = int.MaxValue; + int rangeLastLine = -1; + Location target = null; + + //Console.WriteLine ("FindLocationByMethod: method = {0}, file = {1}, line = {2}, column = {3}", method.Name, Path.GetFileName (file), line, column); + + foreach (var location in method.Locations) { + var srcFile = location.SourceFile; + + //Console.WriteLine ("\tChecking location {0}:{1},{2}...", Path.GetFileName(srcFile), location.LineNumber, location.ColumnNumber); + + //Check if file names match + if (srcFile != null && PathComparer.Compare (Path.GetFileName (NormalizePath (srcFile)), Path.GetFileName (file)) == 0) { + //Check if full path match(we don't care about md5 if full path match): + //1. For backward compatibility + //2. If full path matches user himself probably modified code and is aware of modifications + //OR if md5 match, useful for alternative location files with breakpoints + if (!PathsAreEqual (NormalizePath (srcFile), file) && !SourceLocation.CheckFileHash (file, location.SourceFileHash)) + continue; + + if (location.LineNumber < rangeFirstLine) + rangeFirstLine = location.LineNumber; + + if (location.LineNumber > rangeLastLine) + rangeLastLine = location.LineNumber; + + if (line >= rangeFirstLine && line <= rangeLastLine) + insideTypeRange = true; + + if (line == location.LineNumber && (column <= 1 || column == location.ColumnNumber)) { + if (target != null) { + if (location.ILOffset < target.ILOffset) + target = location; + } else { + target = location; + } + } + } else { + rangeFirstLine = int.MaxValue; + rangeLastLine = -1; + } + } + + return target; + } + + Location FindLocationByType (TypeMirror type, string file, int line, int column, out bool genericMethod, out bool insideTypeRange) + { + var methodInsideTypeRange = false; + Location target = null; + + insideTypeRange = false; + genericMethod = false; + + foreach (var method in type.GetMethods ()) { + Location location = null; + + if ((location = FindLocationByMethod (method, file, line, column, ref methodInsideTypeRange)) != null) { + insideTypeRange |= methodInsideTypeRange;//If any method returns true return true + + if (target != null) { + // Use the location with the lowest ILOffset + if (location.ILOffset < target.ILOffset) { + genericMethod = IsGenericMethod (method); + target = location; + } + } else { + genericMethod = IsGenericMethod (method); + target = location; + } + } + } + + return target; + } + + void ResolvePendingBreakpoint (BreakInfo bi, Location l) + { + bi.Location = l; + InsertBreakpoint ((Breakpoint) bi.BreakEvent, bi); + bi.SetStatus (BreakEventStatus.Bound, null); + } + + void ResolvePendingCatchpoint (BreakInfo bi, TypeMirror type) + { + InsertCatchpoint ((Catchpoint) bi.BreakEvent, bi, type); + bi.SetStatus (BreakEventStatus.Bound, null); + } + + bool UpdateAssemblyFilters (AssemblyMirror asm) + { + var name = asm.GetName ().FullName; + bool found = false; + if (userAssemblyNames != null) { + //HACK: not sure how else to handle xsp-compiled pages + if (name.StartsWith ("App_", StringComparison.Ordinal)) { + found = true; + } else { + foreach (var n in userAssemblyNames) { + if (n == name) { + found = true; + break; + } + } + } + } + + if (found) { + assemblyFilters.Add (asm); + return true; + } + + return false; + } + + internal void WriteDebuggerOutput (bool isError, string msg) + { + OnDebuggerOutput (isError, msg); + } + + protected override void OnSetActiveThread (long processId, long threadId) + { + } + + protected override void OnStepInstruction () + { + Step (StepDepth.Into, StepSize.Min); + } + + protected override void OnStepLine () + { + Step (StepDepth.Into, StepSize.Line); + } + + protected override void OnStop () + { + vm.Suspend (); + + //emit a stop event at the current position of the most recent thread + //we use "getprocesses" instead of "ongetprocesses" because it attaches the process to the session + //using private Mono.Debugging API, so our thread/backtrace calls will cache stuff that will get used later + var process = GetProcesses () [0]; + EnsureRecentThreadIsValid (process); + current_thread = recent_thread; + OnTargetEvent (new TargetEventArgs (TargetEventType.TargetStopped) { + Process = process, + Thread = GetThread (process, recent_thread), + Backtrace = GetThreadBacktrace (recent_thread)}); + } + + void EnsureRecentThreadIsValid (ProcessInfo process) + { + var infos = process.GetThreads (); + + if (ThreadIsAlive (recent_thread) && HasUserFrame (GetId (recent_thread), infos)) + return; + + var threads = vm.GetThreads (); + foreach (var thread in threads) { + if (ThreadIsAlive (thread) && HasUserFrame (GetId (thread), infos)) { + recent_thread = thread; + return; + } + } + recent_thread = threads[0]; + } + + long GetId (ThreadMirror thread) + { + long id; + if (!localThreadIds.TryGetValue (thread.ThreadId, out id)) { + id = localThreadIds.Count + 1; + localThreadIds [thread.ThreadId] = id; + } + return id; + } + + static bool ThreadIsAlive (ThreadMirror thread) + { + if (thread == null) + return false; + ThreadState state; + try { + state = thread.ThreadState; + } catch (ObjectCollectedException) { + return false;//Thread was already collected by garbage collector, hence it's not alive + } + return state != ThreadState.Stopped && state != ThreadState.Aborted; + } + + //we use the Mono.Debugging classes because they are cached + static bool HasUserFrame (long tid, Mono.Debugging.Client.ThreadInfo[] threads) + { + foreach (var thread in threads) { + if (thread.Id != tid) + continue; + + var bt = thread.Backtrace; + for (int i = 0; i < bt.FrameCount; i++) { + var frame = bt.GetFrame (i); + if (frame != null && !frame.IsExternalCode) + return true; + } + + return false; + } + + return false; + } + + public bool IsExternalCode (StackFrame frame) + { + return frame.Method == null || string.IsNullOrEmpty (frame.FileName) + || (assemblyFilters != null && !assemblyFilters.Contains (frame.Method.DeclaringType.Assembly)); + } + + public bool IsExternalCode (TypeMirror type) + { + return assemblyFilters != null && !assemblyFilters.Contains (type.Assembly); + } + + protected override AssemblyLine[] OnDisassembleFile (string file) + { + List mirrors; + + if (!source_to_type.TryGetValue (file, out mirrors)) + return new AssemblyLine [0]; + + var lines = new List (); + foreach (var type in mirrors) { + foreach (var method in type.GetMethods ()) { + string srcFile = method.SourceFile != null ? NormalizePath (method.SourceFile) : null; + + if (srcFile == null || !PathsAreEqual (srcFile, file)) + continue; + + var body = method.GetMethodBody (); + int lastLine = -1; + int firstPos = lines.Count; + string addrSpace = method.FullName; + + foreach (var ins in body.Instructions) { + var loc = method.LocationAtILOffset (ins.Offset); + + if (loc != null && lastLine == -1) { + lastLine = loc.LineNumber; + for (int n = firstPos; n < lines.Count; n++) { + AssemblyLine old = lines [n]; + lines [n] = new AssemblyLine (old.Address, old.AddressSpace, old.Code, loc.LineNumber); + } + } + + lines.Add (new AssemblyLine (ins.Offset, addrSpace, Disassemble (ins), loc != null ? loc.LineNumber : lastLine)); + } + } + } + + lines.Sort (delegate (AssemblyLine a1, AssemblyLine a2) { + int res = a1.SourceLine.CompareTo (a2.SourceLine); + + return res != 0 ? res : a1.Address.CompareTo (a2.Address); + }); + + return lines.ToArray (); + } + + public AssemblyLine[] Disassemble (StackFrame frame) + { + var body = frame.Method.GetMethodBody (); + var instructions = body.Instructions; + var lines = new List (); + + foreach (var instruction in instructions) { + var location = frame.Method.LocationAtILOffset (instruction.Offset); + int lineNumber = location != null ? location.LineNumber : -1; + var code = Disassemble (instruction); + + lines.Add (new AssemblyLine (instruction.Offset, frame.Method.FullName, code, lineNumber)); + } + + return lines.ToArray (); + } + + public AssemblyLine[] Disassemble (StackFrame frame, int firstLine, int count) + { + var body = frame.Method.GetMethodBody (); + var instructions = body.Instructions; + ILInstruction current = null; + + foreach (var instruction in instructions) { + if (instruction.Offset >= frame.ILOffset) { + current = instruction; + break; + } + } + + if (current == null) + return new AssemblyLine [0]; + + var lines = new List (); + + while (firstLine < 0 && count > 0) { + if (current.Previous == null) { + lines.Add (AssemblyLine.OutOfRange); + firstLine = 0; + break; + } + + current = current.Previous; + firstLine++; + } + + while (current != null && firstLine > 0) { + current = current.Next; + firstLine--; + } + + while (count > 0) { + if (current != null) { + var location = frame.Method.LocationAtILOffset (current.Offset); + int lineNumber = location != null ? location.LineNumber : -1; + var code = Disassemble (current); + + lines.Add (new AssemblyLine (current.Offset, frame.Method.FullName, code, lineNumber)); + current = current.Next; + } else { + lines.Add (AssemblyLine.OutOfRange); + } + + count--; + } + + return lines.ToArray (); + } + + public void AddSourceUpdate (string fileName) + { + sourceUpdates.Add (new SourceUpdate(fileName)); + } + public void AddLineUpdate (int oldLine, int newLine) + { + sourceUpdates.Last().LineUpdates.Add (new Tuple(oldLine, newLine)); + } + public void ApplyChanges (ModuleMirror module, byte[] metadataDelta, byte[] ilDelta, byte[] pdbDelta = null) + { + var rootDomain = VirtualMachine.RootDomain; + var metadataArray = rootDomain.CreateByteArray (metadataDelta); + var ilArray = rootDomain.CreateByteArray (ilDelta); + Value pdbArray; + if (pdbDelta == null) + pdbArray = VirtualMachine.CreateValue (null); + else + pdbArray = rootDomain.CreateByteArray (pdbDelta); + + module.Assembly.ApplyChanges_DebugInformation (metadataDelta, pdbDelta); + module.ApplyChanges (metadataArray, ilArray, pdbArray); + foreach (var sourceUpdate in sourceUpdates) + { + var types = VirtualMachine.GetTypesForSourceFile (sourceUpdate.FileName, false); + foreach (var type in types) + { + type.ApplySourceChanges (sourceUpdate); + } + } + sourceUpdates.Clear (); + } + + public string GetEnCCapabilities() + { + return vm.GetEnCCapabilities (); + } + static string EscapeString (string text) + { + var escaped = new StringBuilder (); + + escaped.Append ('"'); + for (int i = 0; i < text.Length; i++) { + char c = text[i]; + string txt; + switch (c) { + case '"': txt = "\\\""; break; + case '\0': txt = @"\0"; break; + case '\\': txt = @"\\"; break; + case '\a': txt = @"\a"; break; + case '\b': txt = @"\b"; break; + case '\f': txt = @"\f"; break; + case '\v': txt = @"\v"; break; + case '\n': txt = @"\n"; break; + case '\r': txt = @"\r"; break; + case '\t': txt = @"\t"; break; + default: + if (char.GetUnicodeCategory (c) == UnicodeCategory.OtherNotAssigned) { + escaped.AppendFormat ("\\u{0:X4}", c); + } else { + escaped.Append (c); + } + continue; + } + escaped.Append (txt); + } + escaped.Append ('"'); + + return escaped.ToString (); + } + + static string Disassemble (ILInstruction ins) + { + string oper; + if (ins.Operand is MethodMirror) + oper = ((MethodMirror)ins.Operand).FullName; + else if (ins.Operand is FieldInfoMirror) + oper = ((FieldInfoMirror)ins.Operand).FullName; + else if (ins.Operand is TypeMirror) + oper = ((TypeMirror)ins.Operand).FullName; + else if (ins.Operand is ILInstruction) + oper = ((ILInstruction)ins.Operand).Offset.ToString ("x8"); + else if (ins.Operand is string) + oper = EscapeString ((string) ins.Operand); + else if (ins.Operand == null) + oper = string.Empty; + else + oper = ins.Operand.ToString (); + + return ins.OpCode + " " + oper; + } + + readonly static bool IsWindows; + readonly static bool IsMac; + readonly static StringComparer PathComparer; + + static bool IgnoreFilenameCase { + get { return IsMac || IsWindows; } + } + + static SoftDebuggerSession () + { + IsWindows = Path.DirectorySeparatorChar == '\\'; + IsMac = !IsWindows && IsRunningOnMac (); + PathComparer = IgnoreFilenameCase ? StringComparer.OrdinalIgnoreCase : StringComparer.Ordinal; + ThreadMirror.NativeTransitions = true; + } + + //From Managed.Windows.Forms/XplatUI + static bool IsRunningOnMac () + { + IntPtr buf = IntPtr.Zero; + try { + buf = Marshal.AllocHGlobal (8192); + // This is a hacktastic way of getting sysname from uname () + if (uname (buf) == 0) { + string os = Marshal.PtrToStringAnsi (buf); + if (os == "Darwin") + return true; + } + } catch { + return false; + } finally { + if (buf != IntPtr.Zero) + Marshal.FreeHGlobal (buf); + } + return false; + } + + [System.Runtime.InteropServices.DllImport ("libc")] + static extern int uname (IntPtr buf); + } + + class LocationComparer : IComparer + { + public int Compare (Location loc0, Location loc1) + { + if (loc0.LineNumber < loc1.LineNumber) + return -1; + if (loc0.LineNumber > loc1.LineNumber) + return 1; + + if (loc0.ColumnNumber < loc1.ColumnNumber) + return -1; + if (loc0.ColumnNumber > loc1.ColumnNumber) + return 1; + + return loc0.ILOffset - loc1.ILOffset; + } + } + + class BreakInfo: BreakEventInfo + { + public Location Location; + public Dictionary Requests = new Dictionary (); + public string LastConditionValue; + public string FileName; + public string TypeName; + } + + class DisconnectedException: DebuggerException + { + public DisconnectedException (Exception ex): + base ("The connection with the debugger has been lost. The target application may have exited.", ex) + { + } + } + + class DebugSocketException: DebuggerException + { + public DebugSocketException (Exception ex): + base ("Could not open port for debugger. Another process may be using the port.", ex) + { + } + } + + class ConnectionException : DebuggerException + { + public ConnectionException (Exception ex): + base ("Could not connect to the debugger.", ex) + { + } + } +} diff --git a/VisualPascalABCNETLinux/MonoDebugging/Soft/SoftDebuggerStartInfo.cs b/VisualPascalABCNETLinux/MonoDebugging/Soft/SoftDebuggerStartInfo.cs new file mode 100644 index 000000000..6b6453c2c --- /dev/null +++ b/VisualPascalABCNETLinux/MonoDebugging/Soft/SoftDebuggerStartInfo.cs @@ -0,0 +1,215 @@ +// +// SoftDebuggerStartInfo.cs +// +// Author: +// Michael Hutchinson +// +// Copyright (c) 2010 Novell, Inc. (http://www.novell.com) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +using System; +using Mono.Debugging.Client; +using System.Collections.Generic; +using System.Reflection; +using System.IO; +using System.Net; +using Mono.Debugger.Soft; + +namespace Mono.Debugging.Soft +{ + public class SoftDebuggerStartInfo : DebuggerStartInfo + { + public SoftDebuggerStartInfo (string monoRuntimePrefix, Dictionary monoRuntimeEnvironmentVariables) + : this (new SoftDebuggerLaunchArgs (monoRuntimePrefix, monoRuntimeEnvironmentVariables)) + { + } + + public SoftDebuggerStartInfo (SoftDebuggerStartArgs startArgs) + { + if (startArgs == null) + throw new ArgumentNullException ("startArgs"); + this.StartArgs = startArgs; + } + + /// + /// Names of assemblies that are user code. + /// + public List UserAssemblyNames { get; set; } + + /// + /// A mapping of AssemblyNames to their paths. + /// + public Dictionary AssemblyPathMap { get; set; } + + /// + /// A mapping of AssemblyNames to their symbol paths. If no symbols maps are provided, the symbols will be found next to the assemblies in AssemblyPathMap. + /// + public Dictionary SymbolPathMap { get; set; } + + /// + /// The session will output this to the debug log as soon as it starts. It can be used to log warnings from + /// creating the SoftDebuggerStartInfo + /// + public string LogMessage { get; set; } + + /// + /// Args for starting the debugger connection. + /// + public SoftDebuggerStartArgs StartArgs { get; set; } + } + + public interface ISoftDebuggerConnectionProvider + { + IAsyncResult BeginConnect (DebuggerStartInfo dsi, AsyncCallback callback); + void EndConnect (IAsyncResult result, out VirtualMachine vm, out string appName); + void CancelConnect (IAsyncResult result); + bool ShouldRetryConnection (Exception ex); + } + + public abstract class SoftDebuggerStartArgs + { + protected SoftDebuggerStartArgs () + { + MaxConnectionAttempts = 1; + TimeBetweenConnectionAttempts = 500; + } + + public abstract ISoftDebuggerConnectionProvider ConnectionProvider { get; } + + /// + /// Maximum number of connection attempts. Zero or less means infinite attempts. Default is 1. + /// + public int MaxConnectionAttempts { get; set; } + + /// + /// The time between connection attempts, in milliseconds. Default is 500. + /// + public int TimeBetweenConnectionAttempts { get; set; } + } + + public abstract class SoftDebuggerRemoteArgs : SoftDebuggerStartArgs + { + protected SoftDebuggerRemoteArgs (string appName, IPAddress address, int debugPort, int outputPort) + { + if (address == null) + throw new ArgumentNullException ("address"); + if (debugPort < 0) + throw new ArgumentException ("Debug port cannot be less than zero", "debugPort"); + + this.AppName = appName; + this.Address = address; + this.DebugPort = debugPort; + this.OutputPort = outputPort; + } + + /// + /// The IP address for the connection. + /// + public IPAddress Address { get; private set; } + + /// + /// Port for the debugger connection. Zero means random port. + /// + public int DebugPort { get; private set; } + + /// + /// Port for the console connection. Zero means random port, less than zero means that output is not redirected. + /// + public int OutputPort { get; private set; } + + /// + /// Application name that will be shown in the debugger. + /// + public string AppName { get; private set; } + + public bool RedirectOutput { get { return OutputPort >= 0; } } + } + + /// + /// Args for the debugger to listen for an incoming connection from a debuggee. + /// + public sealed class SoftDebuggerListenArgs : SoftDebuggerRemoteArgs + { + public SoftDebuggerListenArgs (string appName, IPAddress address, int debugPort) + : this (appName, address, debugPort, -1) {} + + public SoftDebuggerListenArgs (string appName, IPAddress address, int debugPort, int outputPort) + : base (appName, address, debugPort, outputPort) + { + } + + public override ISoftDebuggerConnectionProvider ConnectionProvider { get { return null; } } + } + + /// + /// Args for the debugger to connect to target that is listening. + /// + public sealed class SoftDebuggerConnectArgs : SoftDebuggerRemoteArgs + { + public SoftDebuggerConnectArgs (string appName, IPAddress address, int debugPort) + : this (appName, address, debugPort, -1) {} + + public SoftDebuggerConnectArgs (string appName, IPAddress address, int debugPort, int outputPort) + : base (appName, address, debugPort, outputPort) + { + if (debugPort == 0) + throw new ArgumentException ("Debug port cannot be zero when connecting", "debugPort"); + if (outputPort == 0) + throw new ArgumentException ("Output port cannot be zero when connecting", "outputPort"); + } + + public override ISoftDebuggerConnectionProvider ConnectionProvider { get { return null; } } + } + + /// + /// Options for the debugger to start a process directly. + /// + public sealed class SoftDebuggerLaunchArgs : SoftDebuggerStartArgs + { + public SoftDebuggerLaunchArgs (string monoRuntimePrefix, Dictionary monoRuntimeEnvironmentVariables) + { + this.MonoRuntimePrefix = monoRuntimePrefix; + this.MonoRuntimeEnvironmentVariables = monoRuntimeEnvironmentVariables; + } + + /// + /// Prefix into which the target Mono runtime is installed. + /// + public string MonoRuntimePrefix { get; private set; } + + /// + /// Environment variables for the Mono runtime. + /// + public Dictionary MonoRuntimeEnvironmentVariables { get; private set; } + + /// + /// Launcher for the external console. May be null if the app does not run on an external console. + /// + public Mono.Debugger.Soft.LaunchOptions.TargetProcessLauncher ExternalConsoleLauncher { get; set; } + + /// + /// Gets or sets the name of the mono executable file. e.g. "mono", "mono32", "mono64"... + /// + public string MonoExecutableFileName { get; set; } + + public override ISoftDebuggerConnectionProvider ConnectionProvider { get { return null; } } + + internal Mono.Debugger.Soft.LaunchOptions.ProcessLauncher CustomProcessLauncher { get; set; } + } +} \ No newline at end of file diff --git a/VisualPascalABCNETLinux/MonoDebugging/Soft/SoftEvaluationContext.cs b/VisualPascalABCNETLinux/MonoDebugging/Soft/SoftEvaluationContext.cs new file mode 100644 index 000000000..4a61b8692 --- /dev/null +++ b/VisualPascalABCNETLinux/MonoDebugging/Soft/SoftEvaluationContext.cs @@ -0,0 +1,226 @@ +// +// SoftEvaluationContext.cs +// +// Author: +// Lluis Sanchez Gual +// +// Copyright (c) 2009 Novell, Inc (http://www.novell.com) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +using System; +using Mono.Debugging.Evaluation; +using Mono.Debugger.Soft; +using DC = Mono.Debugging.Client; +using System.Runtime.CompilerServices; +using System.Threading; + +namespace Mono.Debugging.Soft +{ + public class SoftEvaluationContext: EvaluationContext + { + SoftDebuggerSession session; + int stackVersion; + StackFrame frame; + bool sourceAvailable; + + public ThreadMirror Thread { get; set; } + public AppDomainMirror Domain { get; set; } + + public SoftEvaluationContext (SoftDebuggerSession session, StackFrame frame, DC.EvaluationOptions options): base (options) + { + Frame = frame; + Thread = frame.Thread; + Domain = frame.Domain; + + string method = frame.Method.Name; + if (frame.Method.DeclaringType != null) + method = frame.Method.DeclaringType.FullName + "." + method; + + var sourceLink = session.GetSourceLink(frame.Method, frame.FileName); + + var location = new DC.SourceLocation (method, frame.FileName, frame.LineNumber, frame.ColumnNumber, frame.EndLineNumber, frame.EndColumnNumber, frame.Location.SourceFileHash, sourceLink); + string language; + + if (frame.Method != null) { + language = frame.IsNativeTransition ? "Transition" : "Managed"; + } else { + language = "Native"; + } + + Evaluator = session.GetEvaluator (new DC.StackFrame (frame.ILOffset, location, language, session.IsExternalCode (frame), true)); + Adapter = session.Adaptor; + this.session = session; + stackVersion = session.StackVersion; + sourceAvailable = !string.IsNullOrEmpty (frame.FileName) && System.IO.File.Exists (frame.FileName); + } + + public StackFrame Frame { + get { + if (stackVersion != session.StackVersion) + UpdateFrame (); + return frame; + } + set { + frame = value; + } + } + + public bool SourceCodeAvailable { + get { + if (stackVersion != session.StackVersion) + sourceAvailable = !string.IsNullOrEmpty (Frame.FileName) && System.IO.File.Exists (Frame.FileName); + return sourceAvailable; + } + } + + public SoftDebuggerSession Session { + get { return session; } + } + + public override void WriteDebuggerError (Exception ex) + { + session.WriteDebuggerOutput (true, ex.ToString ()); + } + + public override void WriteDebuggerOutput (string message, params object[] values) + { + session.WriteDebuggerOutput (false, string.Format (message, values)); + } + + public override void CopyFrom (EvaluationContext ctx) + { + base.CopyFrom (ctx); + + var other = (SoftEvaluationContext) ctx; + frame = other.frame; + stackVersion = other.stackVersion; + Thread = other.Thread; + session = other.session; + Domain = other.Domain; + } + + internal static bool IsValueTypeOrPrimitive (TypeMirror type) + { + return type != null && (type.IsValueType || type.IsPrimitive); + } + + static bool IsValueTypeOrPrimitive (Type type) + { + return type != null && (type.IsValueType || type.IsPrimitive); + } + + public Value RuntimeInvoke (MethodMirror method, object target, Value[] values) + { + Value[] outArgs; + return RuntimeInvoke (method, target, values, false, out outArgs); + } + + public Value RuntimeInvoke (MethodMirror method, object target, Value[] values, out Value[] outArgs) + { + return RuntimeInvoke (method, target, values, true, out outArgs); + } + + Value RuntimeInvoke (MethodMirror method, object target, Value[] values, bool enableOutArgs, out Value[] outArgs) + { + outArgs = null; + if (values != null) { + // Some arguments may need to be boxed + var mparams = method.GetParameters (); + if (mparams.Length != values.Length) + throw new EvaluatorException ("Invalid number of arguments when calling: " + method.Name); + + for (int n = 0; n < mparams.Length; n++) { + var tm = mparams[n].ParameterType; + if (tm.IsValueType || tm.IsPrimitive || tm.FullName.StartsWith ("System.Nullable`1", StringComparison.Ordinal)) + continue; + + var type = Adapter.GetValueType (this, values[n]); + var argTypeMirror = type as TypeMirror; + var argType = type as Type; + + if (IsValueTypeOrPrimitive (argTypeMirror) || IsValueTypeOrPrimitive (argType)) { + // A value type being assigned to a parameter which is not a value type. The value has to be boxed. + try { + values[n] = Thread.Domain.CreateBoxedValue (values [n]); + } catch (NotSupportedException) { + // This runtime doesn't support creating boxed values + throw new EvaluatorException ("This runtime does not support creating boxed values."); + } + } + } + } + + if (!method.IsStatic && method.DeclaringType.IsClass && !IsValueTypeOrPrimitive (method.DeclaringType)) { + object type = Adapter.GetValueType (this, target); + var targetTypeMirror = type as TypeMirror; + var targetType = type as Type; + + if ((target is StructMirror && ((StructMirror) target).Type != method.DeclaringType) || + (IsValueTypeOrPrimitive (targetTypeMirror) || IsValueTypeOrPrimitive (targetType))) { + // A value type being assigned to a parameter which is not a value type. The value has to be boxed. + try { + target = Thread.Domain.CreateBoxedValue ((Value) target); + } catch (NotSupportedException) { + // This runtime doesn't support creating boxed values + throw new EvaluatorException ("This runtime does not support creating boxed values."); + } + } + } + + try { + return method.Evaluate (target is TypeMirror ? null : (Value) target, values); + } catch (NotSupportedException) { + AssertTargetInvokeAllowed (); + var threadState = Thread.ThreadState; + if ((threadState & ThreadState.WaitSleepJoin) == ThreadState.WaitSleepJoin) { + DC.DebuggerLoggingService.LogMessage ("Thread state before evaluation is {0}", threadState); + throw new EvaluatorException ("Evaluation is not allowed when the thread is in 'Wait' state"); + } + var mc = new MethodCall (this, method, target, values, enableOutArgs); + //Since runtime is returning NOT_SUSPENDED error if two methods invokes are executed + //at same time we have to lock invoking to prevent this... + lock (method.VirtualMachine) { + Adapter.AsyncExecute (mc, Options.EvaluationTimeout); + } + if (enableOutArgs) { + outArgs = mc.OutArgs; + } + return mc.ReturnValue; + } + } + + void UpdateFrame () + { + stackVersion = session.StackVersion; + foreach (StackFrame f in Thread.GetFrames ()) { + if (f.FileName == Frame.FileName && f.LineNumber == Frame.LineNumber && f.ILOffset == Frame.ILOffset) { + Frame = f; + break; + } + } + } + + public override bool SupportIEnumerable { + get { + return session.VirtualMachine.Version.AtLeast (2, 35); + } + } + } +} diff --git a/VisualPascalABCNETLinux/MonoDebugging/Soft/SoftValueReference.cs b/VisualPascalABCNETLinux/MonoDebugging/Soft/SoftValueReference.cs new file mode 100644 index 000000000..da9302096 --- /dev/null +++ b/VisualPascalABCNETLinux/MonoDebugging/Soft/SoftValueReference.cs @@ -0,0 +1,25 @@ +using System; +using Mono.Debugging.Evaluation; +using Mono.Debugger.Soft; + +namespace Mono.Debugging.Soft +{ + public abstract class SoftValueReference: ValueReference + { + public SoftValueReference (EvaluationContext ctx) : base(ctx) + { + } + + protected void EnsureContextHasDomain (AppDomainMirror domain) + { + var softEvaluationContext = (SoftEvaluationContext)Context; + if (softEvaluationContext.Domain == domain) + return; + + var clone = (SoftEvaluationContext)softEvaluationContext.Clone (); + clone.Domain = domain; + Context = clone; + } + } +} + diff --git a/VisualPascalABCNETLinux/MonoDebugging/Soft/SourceLinkMap.cs b/VisualPascalABCNETLinux/MonoDebugging/Soft/SourceLinkMap.cs new file mode 100644 index 000000000..f4fa384c6 --- /dev/null +++ b/VisualPascalABCNETLinux/MonoDebugging/Soft/SourceLinkMap.cs @@ -0,0 +1,14 @@ +namespace Mono.Debugging.Soft +{ + internal class SourceLinkMap + { + public string RelativePathWildcard { get; } + public string UriWildcard { get; } + + public SourceLinkMap (string relativePathWildcard, string uriWildcard) + { + UriWildcard = uriWildcard; + RelativePathWildcard = relativePathWildcard; + } + } +} diff --git a/VisualPascalABCNETLinux/MonoDebugging/Soft/StringAdaptor.cs b/VisualPascalABCNETLinux/MonoDebugging/Soft/StringAdaptor.cs new file mode 100644 index 000000000..95502a049 --- /dev/null +++ b/VisualPascalABCNETLinux/MonoDebugging/Soft/StringAdaptor.cs @@ -0,0 +1,75 @@ +// +// StringAdaptor.cs +// +// Author: Jeffrey Stedfast +// +// Copyright (c) 2012 Xamarin Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +using Mono.Debugging.Evaluation; +using Mono.Debugger.Soft; + +namespace Mono.Debugging.Soft +{ + public class StringAdaptor: IStringAdaptor + { + readonly bool atleast_2_10; + readonly StringMirror str; + string val; + + public StringAdaptor (StringMirror str) + { + atleast_2_10 = str.VirtualMachine.Version.AtLeast (2, 10); + this.str = str; + } + + public int Length { + get { + if (atleast_2_10) + return str.Length; + + if (val == null) + val = str.Value; + + return val.Length; + } + } + + public string Value { + get { + if (val == null) + val = str.Value; + + return val; + } + } + + public string Substring (int index, int length) + { + if (val == null && atleast_2_10) + return new string (str.GetChars (index, length)); + + if (val == null) + val = str.Value; + + return val.Substring (index, length); + } + } +} diff --git a/VisualPascalABCNETLinux/MonoDebugging/Soft/Subprocess.cs b/VisualPascalABCNETLinux/MonoDebugging/Soft/Subprocess.cs new file mode 100644 index 000000000..c1fea1e63 --- /dev/null +++ b/VisualPascalABCNETLinux/MonoDebugging/Soft/Subprocess.cs @@ -0,0 +1,223 @@ +// +// Subprocess.cs +// +// Author: +// Lluis Sanchez +// +// Copyright (c) 2020 Microsoft +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Text; +using System.Threading; +using Mono.Debugger.Soft; +using Mono.Debugging.Backend; +using Mono.Debugging.Client; +using Mono.Debugging.Evaluation; + +namespace Mono.Debugging.Soft +{ + class Subprocess + { + const string ArgStartMarker = "_ARG_START_"; + readonly SoftDebuggerSession softDebuggerSession; + + ManualResetEvent launchingEvent = new ManualResetEvent (false); + ManualResetEvent startedEvent = new ManualResetEvent (false); + + string arguments; + string exe; + + int processId; + bool shutdown; + + ProcessStartInfo debuggerStartInfo; + + SoftDebuggerSession newSession; + + public SoftDebuggerSession SubprocessSession => newSession; + + public long ThreadId { get; private set; } + + public Subprocess (long threadId, SoftDebuggerSession softDebuggerSession) + { + ThreadId = threadId; + this.softDebuggerSession = softDebuggerSession; + } + + public static bool IsMonoLauncher (string filePath) + { + var exeName = Path.GetFileName (filePath); + return exeName == "mono" || exeName == "mono64"; + } + + public bool CreateSession (ValueReference startInfo, EvaluationOptions ops) + { + // Get the exe and arguments from the start info and store them + this.arguments = (string)startInfo.GetChild ("Arguments", ops).GetRawValue (ops); + this.exe = (string)startInfo.GetChild ("FileName", ops).GetRawValue (ops); + + // Create the new session and custom start arguments. + // The actual arguments don't matter much since we'll use a custom launcher for the process. + newSession = new SoftDebuggerSession (); + + var startArgs = new SoftDebuggerLaunchArgs (null, new Dictionary ()); + var dsi = new SoftDebuggerStartInfo (startArgs); + + // A mono process can be launched either by providing "mono" as launcher and then the assembly name as argument, + // or by providing the assembly name directly as file to launch. + + bool explicitMonoLaunch = IsMonoLauncher (exe); + if (explicitMonoLaunch) { + // Try to get the assembly name from the command line. The debug session uses what's provided + // in the Command property as process name. Without this, all mono processes launched in this + // way would show up as "mono" in the IDE. + var cmd = GetExeName (arguments); + if (cmd != null) + dsi.Command = cmd; + + // The new session will add additional runtime arguments to the start info. + // We'll use the _ARG_START_ marker to know where those arguments end. + dsi.RuntimeArguments = ArgStartMarker; + } else { + dsi.Command = exe; + dsi.Arguments = arguments; + } + + startArgs.CustomProcessLauncher = TargetProcessLauncher; + + // Start the session. This will run asynchronously, and will at some point call the custom launcher specified just above. + newSession.Run (dsi, softDebuggerSession.Options); + + // Wait for the session to ask for the process to be launched + launchingEvent.WaitOne (); + if (shutdown) + return false; + + // The custom launcher will store the debugger agent configuration args in debuggerStartInfo. + // No the original startInfo object is patched to include the debugger agent args + + if (explicitMonoLaunch) { + // Prepend the debugger args to the original arguments + int i = debuggerStartInfo.Arguments.IndexOf (ArgStartMarker); + var debuggerArgs = debuggerStartInfo.Arguments.Substring (0, i); + startInfo.GetChild ("Arguments", ops).SetRawValue (debuggerArgs + " " + arguments, ops); + } else { + startInfo.GetChild ("Arguments", ops).SetRawValue (debuggerStartInfo.Arguments, ops); + startInfo.GetChild ("FileName", ops).SetRawValue (debuggerStartInfo.FileName, ops); + } + return true; + } + + Process TargetProcessLauncher (ProcessStartInfo info) + { + // This callback is called by the debug session to start the process. + // We won't actually launch the process here since the current process is already doing it, + // we just need to wait for the process to be launched by resuming execution, and then return + // a reference to that process. + + // Store a reference to the start info provided to start the process. We need to retrieve the + // debug agent configuration arguments from it. + debuggerStartInfo = info; + + info.RedirectStandardError = false; + info.RedirectStandardOutput = false; + + // Signal that the session asked the process to be started (this will cause execution to be resumed, + // so the process will be launched) + launchingEvent.Set (); + + // Wait for the process to start + startedEvent.WaitOne (); + + // processId should be set now. We can now return the process that has launched. + var process = Process.GetProcessById (processId); + process.EnableRaisingEvents = true; + return process; + } + + public void SetStarted (ValueReference startInfo, int id, EvaluationContext ctx) + { + // This is called after the process is launched. + + // Store the process ID. It will be used by TargetProcessLauncher to get a reference to the process. + processId = id; + + // Assign the original exe and arguments to the start info object + startInfo.GetChild ("Arguments", ctx.Options).SetRawValue (arguments, ctx.Options); + startInfo.GetChild ("FileName", ctx.Options).SetRawValue (exe, ctx.Options); + + // Signal TargetProcessLauncher that the process has started and the process ID is available. + startedEvent.Set (); + } + + public static string GetExeName (string monoCommandArgs) + { + foreach (var arg in GetArguments (monoCommandArgs)) { + if (arg.StartsWith ("--")) + continue; + var ext = Path.GetExtension (arg).ToLower (); + if ((ext == ".dll" || ext == ".exe") && File.Exists (arg)) + return arg; + } + return null; + } + + static IEnumerable GetArguments (string monoCommandArgs) + { + bool inQuotes = false; + var currentString = new StringBuilder (); + for (int n = 0; n < monoCommandArgs.Length; n++) { + var c = monoCommandArgs[n]; + if (c == '\\') { + if (n < monoCommandArgs.Length - 1 && (monoCommandArgs[n + 1] == '\\' || monoCommandArgs[n + 1] == '"')) { + currentString.Append (monoCommandArgs[n + 1]); + n++; + } + } else if (inQuotes) { + if (c == '"') + inQuotes = false; + else + currentString.Append (c); + } else if (c == '"') { + inQuotes = true; + } else if (char.IsWhiteSpace (c)) { + if (currentString.Length > 0) { + yield return currentString.ToString (); + currentString.Clear (); + } + } else + currentString.Append (c); + } + if (currentString.Length > 0) + yield return currentString.ToString (); + } + + public void Shutdown () + { + shutdown = true; + launchingEvent.Set (); + startedEvent.Set (); + } + } +} diff --git a/VisualPascalABCNETLinux/MonoDebugging/Soft/ThisValueReference.cs b/VisualPascalABCNETLinux/MonoDebugging/Soft/ThisValueReference.cs new file mode 100644 index 000000000..a3538697d --- /dev/null +++ b/VisualPascalABCNETLinux/MonoDebugging/Soft/ThisValueReference.cs @@ -0,0 +1,79 @@ +// +// ThisValueReference.cs +// +// Author: Jeffrey Stedfast +// +// Copyright (c) 2014 Xamarin Inc. (www.xamarin.com) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +using System; + +using Mono.Debugging.Evaluation; +using Mono.Debugging.Client; +using Mono.Debugger.Soft; + +using StackFrame = Mono.Debugger.Soft.StackFrame; + +namespace Mono.Debugging.Soft +{ + public class ThisValueReference : ValueReference + { + readonly StackFrame frame; + object type; + Value value; + + public ThisValueReference (EvaluationContext ctx, StackFrame frame) : base (ctx) + { + this.frame = frame; + } + + public override ObjectValueFlags Flags { + get { return ObjectValueFlags.Field | ObjectValueFlags.ReadOnly; } + } + + public override string Name { + get { return "this"; } + } + + public override object Value { + get { + if (value == null) + value = frame.GetThis (); + + return value; + } + set { + if (frame.VirtualMachine.Version.AtLeast (2, 44)) { + this.value = (Value)value; + frame.SetThis ((Value)value); + } + } + } + + public override object Type { + get { + if (type == null) + type = Context.Adapter.GetValueType (Context, Value); + + return type; + } + } + } +} diff --git a/VisualPascalABCNETLinux/MonoDebugging/Soft/VariableValueReference.cs b/VisualPascalABCNETLinux/MonoDebugging/Soft/VariableValueReference.cs new file mode 100644 index 000000000..8a3e0fd94 --- /dev/null +++ b/VisualPascalABCNETLinux/MonoDebugging/Soft/VariableValueReference.cs @@ -0,0 +1,145 @@ +// +// VariableValueReference.cs +// +// Author: +// Lluis Sanchez Gual +// +// Copyright (c) 2009 Novell, Inc (http://www.novell.com) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +using System; + +using Mono.Debugging.Evaluation; +using Mono.Debugging.Client; +using Mono.Debugger.Soft; + +namespace Mono.Debugging.Soft +{ + public class VariableValueReference : ValueReference + { + readonly LocalVariableBatch batch; + readonly LocalVariable variable; + readonly string name; + Value value; + int version; + + public VariableValueReference (EvaluationContext ctx, string name, LocalVariable variable, LocalVariableBatch batch) : base (ctx) + { + this.variable = variable; + this.batch = batch; + this.name = name; + } + + public VariableValueReference (EvaluationContext ctx, string name, LocalVariable variable, Value value) : base (ctx) + { + version = ((SoftEvaluationContext)ctx).Session.StackVersion; + this.variable = variable; + this.value = value; + this.name = name; + } + + public VariableValueReference (EvaluationContext ctx, string name, LocalVariable variable) : base (ctx) + { + this.variable = variable; + this.name = name; + } + + public override ObjectValueFlags Flags { + get { + return ObjectValueFlags.Variable; + } + } + + public override string Name { + get { + return name; + } + } + + public override object Type { + get { + return variable.Type; + } + } + + Value NormalizeValue (EvaluationContext ctx, Value value) + { + if (variable.Type.IsPointer) { + long addr = (long) ((PrimitiveValue) value).Value; + + return new PointerValue (value.VirtualMachine, variable.Type, addr); + } + + return ctx.Adapter.IsNull (ctx, value) ? null : value; + } + + object GetValue (SoftEvaluationContext ctx) + { + try { + if (value == null || version != ctx.Session.StackVersion) { + value = batch != null ? batch.GetValue (ctx, variable) : ctx.Frame.GetValue (variable); + version = ctx.Session.StackVersion; + } + + return NormalizeValue (ctx, value); + } catch (AbsentInformationException ex) { + throw new EvaluatorException (ex, "Value not available"); + } catch (Exception ex) { + throw new EvaluatorException (ex.Message); + } + } + + void SetValue (SoftEvaluationContext ctx, object value) + { + if (batch != null) { + batch.SetValue (ctx, variable, (Value) value); + } else { + ctx.Frame.SetValue (variable, (Value) value); + ctx.Session.StackVersion++; + } + version = ctx.Session.StackVersion; + this.value = (Value) value; + } + + public override object GetValue (EvaluationContext ctx) + { + return GetValue ((SoftEvaluationContext) ctx); + } + + public override void SetValue (EvaluationContext ctx, object value) + { + SetValue ((SoftEvaluationContext) ctx, value); + } + + public override object Value { + get { + return GetValue ((SoftEvaluationContext) Context); + } + set { + SetValue ((SoftEvaluationContext) Context, value); + } + } + + internal string [] GetTupleElementNames (SoftEvaluationContext ctx) + { + return ctx.Session.GetPdbData (variable.Method)?.GetTupleElementNames (variable.Method, variable.Index); + } + } +} diff --git a/VisualPascalABCNETLinux/VisualPascalABCNETLinux.csproj b/VisualPascalABCNETLinux/VisualPascalABCNETLinux.csproj index 963c0084a..66c128e41 100644 --- a/VisualPascalABCNETLinux/VisualPascalABCNETLinux.csproj +++ b/VisualPascalABCNETLinux/VisualPascalABCNETLinux.csproj @@ -299,6 +299,187 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + UserControl @@ -790,6 +971,7 @@ +