diff --git a/src/debugger/breakpoints_line.cpp b/src/debugger/breakpoints_line.cpp index 8fb3705a..1214b193 100644 --- a/src/debugger/breakpoints_line.cpp +++ b/src/debugger/breakpoints_line.cpp @@ -19,7 +19,9 @@ void LineBreakpoints::ManagedLineBreakpoint::ToBreakpoint(Breakpoint &breakpoint breakpoint.condition = this->condition; breakpoint.source = Source(fullname); breakpoint.line = this->linenum; + breakpoint.column = this->column; breakpoint.endLine = this->endLine; + breakpoint.endColumn = this->endColumn; breakpoint.hitCount = this->times; } @@ -169,7 +171,7 @@ static HRESULT ResolveLineBreakpoint(Modules *pModules, ICorDebugModule *pModule else if (pModule) // Filter data from only one module during resolve, if need. IfFailRet(pModule->GetBaseAddress(&modAddress)); - IfFailRet(pModules->ResolveBreakpoint(modAddress, bp_fullname, bp_fullname_index, bp.linenum, resolvedPoints)); + IfFailRet(pModules->ResolveBreakpoint(modAddress, bp_fullname, bp_fullname_index, bp.linenum, bp.column, resolvedPoints)); if (resolvedPoints.empty()) return E_FAIL; @@ -224,6 +226,8 @@ static HRESULT ActivateLineBreakpoint(LineBreakpoints::ManagedLineBreakpoint &bp // same for multiple breakpoint resolve for one module bp.linenum = resolvedPoints[0].startLine; bp.endLine = resolvedPoints[0].endLine; + bp.column = resolvedPoints[0].startColumn; + bp.endColumn = resolvedPoints[0].endColumn; bp.modAddress = modAddress; return S_OK; @@ -245,6 +249,7 @@ HRESULT LineBreakpoints::ManagedCallbackLoadModule(ICorDebugModule *pModule, std bp.module = initialBreakpoint.breakpoint.module; bp.enabled = initialBreakpoint.enabled; bp.linenum = initialBreakpoint.breakpoint.line; + bp.column = initialBreakpoint.breakpoint.column; bp.endLine = initialBreakpoint.breakpoint.line; bp.condition = initialBreakpoint.breakpoint.condition; unsigned resolved_fullname_index = 0; @@ -324,12 +329,13 @@ HRESULT LineBreakpoints::UpdateLineBreakpoint(bool haveProcess, int id, int line bp.module = initialBreakpoint.breakpoint.module; bp.enabled = initialBreakpoint.enabled; bp.linenum = initialBreakpoint.breakpoint.line; + bp.column = initialBreakpoint.breakpoint.column; bp.endLine = initialBreakpoint.breakpoint.line; bp.condition = initialBreakpoint.breakpoint.condition; unsigned resolved_fullname_index = 0; std::vector resolvedPoints; - if (FAILED(m_sharedModules->ResolveBreakpoint(modAddress, initialBreakpoints.first, resolved_fullname_index, bp.linenum, resolvedPoints)) || + if (FAILED(m_sharedModules->ResolveBreakpoint(modAddress, initialBreakpoints.first, resolved_fullname_index, bp.linenum, bp.column, resolvedPoints)) || FAILED(ActivateLineBreakpoint(bp, initialBreakpoints.first, m_justMyCode, resolvedPoints))) { return S_OK; @@ -434,6 +440,7 @@ HRESULT LineBreakpoints::SetLineBreakpoints(bool haveProcess, const std::string& for (const auto &sb : lineBreakpoints) { int line = sb.line; + int column = sb.column; Breakpoint breakpoint; auto b = breakpointsInSourceMap.find(line); @@ -448,6 +455,7 @@ HRESULT LineBreakpoints::SetLineBreakpoints(bool haveProcess, const std::string& bp.id = initialBreakpoint.id; bp.module = initialBreakpoint.breakpoint.module; bp.linenum = line; + bp.column = column; bp.endLine = line; bp.condition = initialBreakpoint.breakpoint.condition; unsigned resolved_fullname_index = 0; @@ -511,6 +519,7 @@ HRESULT LineBreakpoints::SetLineBreakpoints(bool haveProcess, const std::string& bp.id = initialBreakpoint.id; bp.module = initialBreakpoint.breakpoint.module; bp.linenum = line; + bp.column = column; bp.endLine = line; bp.condition = initialBreakpoint.breakpoint.condition; bp.ToBreakpoint(breakpoint, filename); @@ -573,6 +582,7 @@ HRESULT LineBreakpoints::UpdateBreakpointsOnHotReload(ICorDebugModule *pModule, bp.module = initialBreakpoint.breakpoint.module; bp.enabled = initialBreakpoint.enabled; bp.linenum = initialBreakpoint.breakpoint.line; + bp.column = initialBreakpoint.breakpoint.column; bp.endLine = initialBreakpoint.breakpoint.line; bp.condition = initialBreakpoint.breakpoint.condition; unsigned resolved_fullname_index = 0; diff --git a/src/debugger/breakpoints_line.h b/src/debugger/breakpoints_line.h index af03a28a..bffa0177 100644 --- a/src/debugger/breakpoints_line.h +++ b/src/debugger/breakpoints_line.h @@ -62,7 +62,9 @@ class LineBreakpoints std::string module; CORDB_ADDRESS modAddress; int linenum; + int column; int endLine; + int endColumn; bool enabled; ULONG32 times; std::string condition; @@ -73,7 +75,7 @@ class LineBreakpoints bool IsVerified() const { return !iCorFuncBreakpoints.empty(); } ManagedLineBreakpoint() : - id(0), modAddress(0), linenum(0), endLine(0), enabled(true), times(0) + id(0), modAddress(0), linenum(0), column(0), endLine(0), endColumn(0), enabled(true), times(0) {} ~ManagedLineBreakpoint() @@ -107,7 +109,7 @@ class LineBreakpoints unsigned resolved_fullname_index; int resolved_linenum; // if int is 0 - no resolved breakpoint available in m_lineResolvedBreakpoints - ManagedLineBreakpointMapping() : breakpoint("", 0, ""), id(0), enabled(true), resolved_fullname_index(0), resolved_linenum(0) {} + ManagedLineBreakpointMapping() : breakpoint("", 0, 0, ""), id(0), enabled(true), resolved_fullname_index(0), resolved_linenum(0) {} ~ManagedLineBreakpointMapping() = default; }; diff --git a/src/interfaces/types.h b/src/interfaces/types.h index 019d1692..95d95f95 100644 --- a/src/interfaces/types.h +++ b/src/interfaces/types.h @@ -241,7 +241,9 @@ struct Breakpoint std::string message; Source source; int line; + int column; int endLine; + int endColumn; uint32_t hitCount; // exposed for MI protocol std::string condition; @@ -249,7 +251,7 @@ struct Breakpoint std::string funcname; std::string params; - Breakpoint() : id(0), verified(false), line(0), endLine(0), hitCount(0) {} + Breakpoint() : id(0), verified(false), line(0), column(0), endLine(0), endColumn(0), hitCount(0) {} }; enum SymbolStatus @@ -434,13 +436,16 @@ struct LineBreakpoint { std::string module; int line; + int column; std::string condition; LineBreakpoint(const std::string &module, int linenum, + int col = 0, const std::string &cond = std::string()) : module(module), line(linenum), + column(col), condition(cond) {} }; diff --git a/src/managed/SymbolReader.cs b/src/managed/SymbolReader.cs index 4440c54b..cdb45a45 100644 --- a/src/managed/SymbolReader.cs +++ b/src/managed/SymbolReader.cs @@ -695,13 +695,17 @@ internal struct resolved_bp_t { public int startLine; public int endLine; + public int startColumn; + public int endColumn; public int ilOffset; public int methodToken; - public resolved_bp_t(int startLine_, int endLine_, int ilOffset_, int methodToken_) + public resolved_bp_t(int startLine_, int endLine_, int startColumn_, int endColumn_, int ilOffset_, int methodToken_) { startLine = startLine_; endLine = endLine_; + startColumn = startColumn_; + endColumn = endColumn_; ilOffset = ilOffset_; methodToken = methodToken_; } @@ -722,7 +726,7 @@ enum Position { /// entry's count in data /// pointer to memory with result /// "Ok" if information is available - internal static RetCode ResolveBreakPoints(IntPtr symbolReaderHandles, int tokenNum, IntPtr Tokens, int sourceLine, int nestedToken, + internal static RetCode ResolveBreakPoints(IntPtr symbolReaderHandles, int tokenNum, IntPtr Tokens, int sourceLine, int sourceColumn, int nestedToken, out int Count, [MarshalAs(UnmanagedType.LPWStr)] string sourcePath, out IntPtr data) { Debug.Assert(symbolReaderHandles != IntPtr.Zero); @@ -746,7 +750,7 @@ internal static RetCode ResolveBreakPoints(IntPtr symbolReaderHandles, int token // We need check if nestedToken's method code closer to sourceLine than code from methodToken's method. // If sourceLine closer to nestedToken's method code - setup breakpoint in nestedToken's method. - SequencePoint SequencePointForSourceLine(Position reqPos, ref MetadataReader reader, int methodToken) + SequencePoint SequencePointForSourceLine(Position reqPos, ref MetadataReader reader, int methodToken, bool filterByColumn = false) { // Note, SequencePoints ordered by IL offsets, not by line numbers. // For example, infinite loop `while(true)` will have IL offset after cycle body's code. @@ -757,6 +761,12 @@ SequencePoint SequencePointForSourceLine(Position reqPos, ref MetadataReader rea if (p.StartLine == 0 || p.StartLine == SequencePoint.HiddenLine || p.EndLine < sourceLine) continue; + // Skip sequence points that start strictly after our target column. + // Use > (not >=) so a cursor placed exactly at a statement's start column is kept. + // Only applied for the outer method — nested token calls use original line-only logic. + if (filterByColumn && sourceColumn > 0 && p.StartLine == sourceLine && p.StartColumn > sourceColumn) + continue; + // Note, in case of constructors, we must care about source too, since we may have situation when field/property have same line in another source. var fileName = reader.GetString(reader.GetDocument(p.Document).Name); if (fileName != sourcePath) @@ -777,8 +787,16 @@ SequencePoint SequencePointForSourceLine(Position reqPos, ref MetadataReader rea } else { - if ((reqPos == Position.First && p.EndColumn < nearestSP.EndColumn) || - (reqPos == Position.Last && p.EndColumn > nearestSP.EndColumn)) + // For column-aware outer-method selection: prefer the sequence point whose + // StartColumn is closest to (largest, not exceeding) the target column. + // For nested token calls and plain line breakpoints: keep original EndColumn logic. + if (filterByColumn && sourceColumn > 0 && p.StartLine == nearestSP.StartLine) + { + if (p.StartColumn > nearestSP.StartColumn) + nearestSP = p; + } + else if ((reqPos == Position.First && p.EndColumn < nearestSP.EndColumn) || + (reqPos == Position.Last && p.EndColumn > nearestSP.EndColumn)) nearestSP = p; } } @@ -794,7 +812,7 @@ SequencePoint SequencePointForSourceLine(Position reqPos, ref MetadataReader rea MetadataReader reader = ((OpenedReader)gch.Target).Reader; int methodToken = Marshal.ReadInt32(Tokens, i * elementSize); - SequencePoint current_p = SequencePointForSourceLine(Position.First, ref reader, methodToken); + SequencePoint current_p = SequencePointForSourceLine(Position.First, ref reader, methodToken, filterByColumn: true); // Note, we don't check that current_p was found or not, since we know for sure, that sourceLine could be resolved in method. // Same idea for nested_p below, if we have nestedToken - it will be resolved for sure. @@ -812,8 +830,17 @@ SequencePoint SequencePointForSourceLine(Position reqPos, ref MetadataReader rea if ((nested_start_p.StartLine > current_p.StartLine || (nested_start_p.StartLine == current_p.StartLine && nested_start_p.StartColumn > current_p.StartColumn)) && (nested_end_p.EndLine < current_p.EndLine || (nested_end_p.EndLine == current_p.EndLine && nested_end_p.EndColumn < current_p.EndColumn )) ) { - list.Add(new resolved_bp_t(current_p.StartLine, current_p.EndLine, current_p.Offset, methodToken)); - break; + // Nested is fully within current_p's range. + // If column info places the cursor inside the nested body, fall through to + // condition 2 so the breakpoint lands in the lambda, not the outer call. + bool cursorInsideNested = sourceColumn > 0 + && sourceColumn >= nested_start_p.StartColumn + && sourceColumn <= nested_end_p.EndColumn; + if (!cursorInsideNested) + { + list.Add(new resolved_bp_t(current_p.StartLine, current_p.EndLine, current_p.StartColumn, current_p.EndColumn, current_p.Offset, methodToken)); + break; + } } // Note, sequence points can't partially overlap each other, since same lemmas can't belong to 2 different sequence points for sure. @@ -821,7 +848,7 @@ SequencePoint SequencePointForSourceLine(Position reqPos, ref MetadataReader rea // current method sequence point and first nested method sequence point. if (current_p.EndLine > nested_start_p.EndLine || (current_p.EndLine == nested_start_p.EndLine && current_p.EndColumn > nested_start_p.EndColumn)) { - list.Add(new resolved_bp_t(nested_start_p.StartLine, nested_start_p.EndLine, nested_start_p.Offset, nestedToken)); + list.Add(new resolved_bp_t(nested_start_p.StartLine, nested_start_p.EndLine, nested_start_p.StartColumn, nested_start_p.EndColumn, nested_start_p.Offset, nestedToken)); // (tokenNum > 1) can have only lines, that added to multiple constructors, in this case - we will have same for all Tokens, // we need unique tokens only for breakpoints, prevent adding nestedToken multiple times. break; @@ -829,7 +856,7 @@ SequencePoint SequencePointForSourceLine(Position reqPos, ref MetadataReader rea } nestedToken = 0; // Don't check nested block next cycle (will have same results). - list.Add(new resolved_bp_t(current_p.StartLine, current_p.EndLine, current_p.Offset, methodToken)); + list.Add(new resolved_bp_t(current_p.StartLine, current_p.EndLine, current_p.StartColumn, current_p.EndColumn, current_p.Offset, methodToken)); } if (list.Count == 0) diff --git a/src/managed/interop.cpp b/src/managed/interop.cpp index 7396dbf6..7dc3a341 100644 --- a/src/managed/interop.cpp +++ b/src/managed/interop.cpp @@ -65,7 +65,7 @@ typedef RetCode (*GetSequencePointsDelegate)(PVOID, mdMethodDef, PVOID*, int32_ typedef RetCode (*GetNextUserCodeILOffsetDelegate)(PVOID, mdMethodDef, uint32_t, uint32_t*, int32_t*); typedef RetCode (*GetStepRangesFromIPDelegate)(PVOID, int32_t, mdMethodDef, uint32_t*, uint32_t*); typedef RetCode (*GetModuleMethodsRangesDelegate)(PVOID, uint32_t, PVOID, uint32_t, PVOID, PVOID*); -typedef RetCode (*ResolveBreakPointsDelegate)(PVOID[], int32_t, PVOID, int32_t, int32_t, int32_t*, const WCHAR*, PVOID*); +typedef RetCode (*ResolveBreakPointsDelegate)(PVOID[], int32_t, PVOID, int32_t, int32_t, int32_t, int32_t*, const WCHAR*, PVOID*); typedef RetCode (*GetAsyncMethodSteppingInfoDelegate)(PVOID, mdMethodDef, PVOID*, int32_t*, uint32_t*); typedef RetCode (*GetSourceDelegate)(PVOID, const WCHAR*, int32_t*, PVOID*); typedef PVOID (*LoadDeltaPdbDelegate)(const WCHAR*, PVOID*, int32_t*); @@ -429,13 +429,13 @@ HRESULT GetModuleMethodsRanges(PVOID pSymbolReaderHandle, uint32_t constrTokensN return retCode == RetCode::OK ? S_OK : E_FAIL; } -HRESULT ResolveBreakPoints(PVOID pSymbolReaderHandles[], int32_t tokenNum, PVOID Tokens, int32_t sourceLine, int32_t nestedToken, int32_t &Count, const std::string &sourcePath, PVOID *data) +HRESULT ResolveBreakPoints(PVOID pSymbolReaderHandles[], int32_t tokenNum, PVOID Tokens, int32_t sourceLine, int32_t sourceColumn, int32_t nestedToken, int32_t &Count, const std::string &sourcePath, PVOID *data) { std::unique_lock read_lock(CLRrwlock.reader); if (!resolveBreakPointsDelegate || !pSymbolReaderHandles || !Tokens || !data) return E_FAIL; - RetCode retCode = resolveBreakPointsDelegate(pSymbolReaderHandles, tokenNum, Tokens, sourceLine, nestedToken, &Count, to_utf16(sourcePath).c_str(), data); + RetCode retCode = resolveBreakPointsDelegate(pSymbolReaderHandles, tokenNum, Tokens, sourceLine, sourceColumn, nestedToken, &Count, to_utf16(sourcePath).c_str(), data); return retCode == RetCode::OK ? S_OK : E_FAIL; } diff --git a/src/managed/interop.h b/src/managed/interop.h index 736f8492..b51f45a1 100644 --- a/src/managed/interop.h +++ b/src/managed/interop.h @@ -95,7 +95,7 @@ namespace Interop HRESULT GetHoistedLocalScopes(PVOID pSymbolReaderHandle, mdMethodDef methodToken, PVOID *data, int32_t &hoistedLocalScopesCount); HRESULT GetStepRangesFromIP(PVOID pSymbolReaderHandle, ULONG32 ip, mdMethodDef MethodToken, ULONG32 *ilStartOffset, ULONG32 *ilEndOffset); HRESULT GetModuleMethodsRanges(PVOID pSymbolReaderHandle, uint32_t constrTokensNum, PVOID constrTokens, uint32_t normalTokensNum, PVOID normalTokens, PVOID *data); - HRESULT ResolveBreakPoints(PVOID pSymbolReaderHandles[], int32_t tokenNum, PVOID Tokens, int32_t sourceLine, int32_t nestedToken, int32_t &Count, const std::string &sourcePath, PVOID *data); + HRESULT ResolveBreakPoints(PVOID pSymbolReaderHandles[], int32_t tokenNum, PVOID Tokens, int32_t sourceLine, int32_t sourceColumn, int32_t nestedToken, int32_t &Count, const std::string &sourcePath, PVOID *data); HRESULT GetAsyncMethodSteppingInfo(PVOID pSymbolReaderHandle, mdMethodDef methodToken, std::vector &AsyncAwaitInfo, ULONG32 *ilOffset); HRESULT GetSource(PVOID symbolReaderHandle, const std::string fileName, PVOID *data, int32_t *length); HRESULT LoadDeltaPdb(const std::string &pdbPath, VOID **ppSymbolReaderHandle, std::unordered_set &methodTokens); diff --git a/src/metadata/modules.cpp b/src/metadata/modules.cpp index 55507e07..ba9ee732 100644 --- a/src/metadata/modules.cpp +++ b/src/metadata/modules.cpp @@ -740,7 +740,7 @@ HRESULT Modules::ForEachModule(std::function } HRESULT Modules::ResolveBreakpoint(/*in*/ CORDB_ADDRESS modAddress, /*in*/ std::string filename, /*out*/ unsigned &fullname_index, - /*in*/ int sourceLine, /*out*/ std::vector &resolvedPoints) + /*in*/ int sourceLine, /*in*/ int sourceColumn, /*out*/ std::vector &resolvedPoints) { #ifdef WIN32 HRESULT Status; @@ -749,7 +749,7 @@ HRESULT Modules::ResolveBreakpoint(/*in*/ CORDB_ADDRESS modAddress, /*in*/ std:: // Note, in all code we use m_modulesInfoMutex > m_sourcesInfoMutex lock sequence. std::lock_guard lockModulesInfo(m_modulesInfoMutex); - return m_modulesSources.ResolveBreakpoint(this, modAddress, filename, fullname_index, sourceLine, resolvedPoints); + return m_modulesSources.ResolveBreakpoint(this, modAddress, filename, fullname_index, sourceLine, sourceColumn, resolvedPoints); } HRESULT Modules::ApplyPdbDeltaAndLineUpdates(ICorDebugModule *pModule, bool needJMC, const std::string &deltaPDB, diff --git a/src/metadata/modules.h b/src/metadata/modules.h index 47705fcb..c98ed806 100644 --- a/src/metadata/modules.h +++ b/src/metadata/modules.h @@ -71,6 +71,7 @@ class Modules /*in*/ std::string filename, /*out*/ unsigned &fullname_index, /*in*/ int sourceLine, + /*in*/ int sourceColumn, /*out*/ std::vector &resolvedPoints); HRESULT GetSourceFullPathByIndex(unsigned index, std::string &fullPath); diff --git a/src/metadata/modules_sources.cpp b/src/metadata/modules_sources.cpp index 82cf91de..5cdf2eaf 100644 --- a/src/metadata/modules_sources.cpp +++ b/src/metadata/modules_sources.cpp @@ -707,7 +707,7 @@ static void LineUpdatesBackwardCorrection(unsigned fullPathIndex, mdMethodDef me } HRESULT ModulesSources::ResolveBreakpoint(/*in*/ Modules *pModules, /*in*/ CORDB_ADDRESS modAddress, /*in*/ std::string filename, /*out*/ unsigned &fullname_index, - /*in*/ int sourceLine, /*out*/ std::vector &resolvedPoints) + /*in*/ int sourceLine, /*in*/ int sourceColumn, /*out*/ std::vector &resolvedPoints) { std::lock_guard lockSourcesInfo(m_sourcesInfoMutex); @@ -739,6 +739,8 @@ HRESULT ModulesSources::ResolveBreakpoint(/*in*/ Modules *pModules, /*in*/ CORDB { int32_t startLine; int32_t endLine; + int32_t startColumn; + int32_t endColumn; uint32_t ilOffset; uint32_t methodToken; }; @@ -805,7 +807,7 @@ HRESULT ModulesSources::ResolveBreakpoint(/*in*/ Modules *pModules, /*in*/ CORDB std::string fullName = m_sourceIndexToInitialFullPath[findIndex->second]; #endif if (FAILED(Interop::ResolveBreakPoints(symbolReaderHandles.data(), (int32_t)Tokens.size(), Tokens.data(), - correctedStartLine, closestNestedToken, Count, fullName, &data)) + correctedStartLine, sourceColumn, closestNestedToken, Count, fullName, &data)) || data == nullptr) { continue; @@ -819,7 +821,9 @@ HRESULT ModulesSources::ResolveBreakpoint(/*in*/ Modules *pModules, /*in*/ CORDB // In case Hot Reload we may have line updates that we must take into account. LineUpdatesForwardCorrection(findIndex->second, inputData.get()[i].methodToken, pmdInfo->m_methodBlockUpdates, inputData.get()[i]); - resolvedPoints.emplace_back(resolved_bp_t(inputData.get()[i].startLine, inputData.get()[i].endLine, inputData.get()[i].ilOffset, + resolvedPoints.emplace_back(resolved_bp_t(inputData.get()[i].startLine, inputData.get()[i].endLine, + inputData.get()[i].startColumn, inputData.get()[i].endColumn, + inputData.get()[i].ilOffset, inputData.get()[i].methodToken, pmdInfo->m_iCorModule.GetPtr())); } } diff --git a/src/metadata/modules_sources.h b/src/metadata/modules_sources.h index 6d59e6d5..4c20573d 100644 --- a/src/metadata/modules_sources.h +++ b/src/metadata/modules_sources.h @@ -136,13 +136,17 @@ class ModulesSources { int32_t startLine; int32_t endLine; + int32_t startColumn; + int32_t endColumn; uint32_t ilOffset; uint32_t methodToken; ToRelease iCorModule; - resolved_bp_t(int32_t startLine_, int32_t endLine_, uint32_t ilOffset_, uint32_t methodToken_, ICorDebugModule *pModule) : + resolved_bp_t(int32_t startLine_, int32_t endLine_, int32_t startColumn_, int32_t endColumn_, uint32_t ilOffset_, uint32_t methodToken_, ICorDebugModule *pModule) : startLine(startLine_), endLine(endLine_), + startColumn(startColumn_), + endColumn(endColumn_), ilOffset(ilOffset_), methodToken(methodToken_), iCorModule(pModule) @@ -155,6 +159,7 @@ class ModulesSources /*in*/ std::string filename, /*out*/ unsigned &fullname_index, /*in*/ int sourceLine, + /*in*/ int sourceColumn, /*out*/ std::vector &resolvedPoints); HRESULT FillSourcesCodeLinesForModule(ICorDebugModule *pModule, IMetaDataImport *pMDImport, PVOID pSymbolReaderHandle); diff --git a/src/protocols/protocol_utils.cpp b/src/protocols/protocol_utils.cpp index 90e3398f..880989b6 100644 --- a/src/protocols/protocol_utils.cpp +++ b/src/protocols/protocol_utils.cpp @@ -61,7 +61,7 @@ HRESULT BreakpointsHandle::SetLineBreakpoint(std::shared_ptr &sharedD for (auto it : breakpointsInSource) lineBreakpoints.push_back(it.second); - lineBreakpoints.emplace_back(module, linenum, condition); + lineBreakpoints.emplace_back(module, linenum, 0, condition); std::vector breakpoints; IfFailRet(sharedDebugger->SetLineBreakpoints(filename, lineBreakpoints, breakpoints)); diff --git a/src/protocols/vscodeprotocol.cpp b/src/protocols/vscodeprotocol.cpp index 7712318b..79f1a42b 100644 --- a/src/protocols/vscodeprotocol.cpp +++ b/src/protocols/vscodeprotocol.cpp @@ -68,8 +68,12 @@ void to_json(json &j, const Breakpoint &b) { {"verified", b.verified}}; if (!b.message.empty()) j["message"] = b.message; + if (b.column > 0) + j["column"] = b.column; if (b.verified) { j["endLine"] = b.endLine; + if (b.endColumn > 0) + j["endColumn"] = b.endColumn; if (!b.source.IsNull()) j["source"] = b.source; } @@ -573,7 +577,7 @@ static HRESULT HandleCommand(std::shared_ptr &sharedDebugger, std::st std::vector lineBreakpoints; for (auto &b : arguments.at("breakpoints")) - lineBreakpoints.emplace_back(std::string(), b.at("line"), b.value("condition", std::string())); + lineBreakpoints.emplace_back(std::string(), b.at("line"), b.value("column", 0), b.value("condition", std::string())); std::vector breakpoints; IfFailRet(sharedDebugger->SetLineBreakpoints(arguments.at("source").at("path"), lineBreakpoints, breakpoints)); diff --git a/test-suite/VSCodeTestSrcBreakpointResolve/Program.cs b/test-suite/VSCodeTestSrcBreakpointResolve/Program.cs index 831a843d..d78c870b 100644 --- a/test-suite/VSCodeTestSrcBreakpointResolve/Program.cs +++ b/test-suite/VSCodeTestSrcBreakpointResolve/Program.cs @@ -91,8 +91,13 @@ public void DebuggerExit(string caller_trace) disconnectRequest.arguments.restart = false; Assert.True(VSCodeDebugger.Request(disconnectRequest).Success, @"__FILE__:__LINE__"+"\n"+caller_trace); } + public int GetBreakpointLine(string bpName) + { + Breakpoint bp = ControlInfo.Breakpoints[bpName]; + return ((LineBreakpoint)bp).NumLine; + } - public void AddBreakpoint(string caller_trace, string bpName, string bpPath = null, string Condition = null) + public void AddBreakpoint(string caller_trace, string bpName, string bpPath = null, string Condition = null, int? Column = 0) { Breakpoint bp = ControlInfo.Breakpoints[bpName]; Assert.Equal(BreakpointType.Line, bp.Type, @"__FILE__:__LINE__"+"\n"+caller_trace); @@ -104,7 +109,7 @@ public void AddBreakpoint(string caller_trace, string bpName, string bpPath = nu listBp = new List(); SrcBreakpoints[sourceFile] = listBp; } - listBp.Add(new SourceBreakpoint(lbp.NumLine, Condition)); + listBp.Add(new SourceBreakpoint(lbp.NumLine, Condition) { column = Column }); List listBpId; if (!SrcBreakpointIds.TryGetValue(sourceFile, out listBpId)) { @@ -209,15 +214,107 @@ public void WasBreakpointHit(string caller_trace, string bpName) throw new ResultNotSuccessException(@"__FILE__:__LINE__"+"\n"+caller_trace); } + public void WasManualBreakpointHitAtColumn(string caller_trace, string bp_fileName, int bp_line, int expectedColumn) + { + Func filter = (resJSON) => { + if (VSCodeDebugger.isResponseContainProperty(resJSON, "event", "stopped") + && VSCodeDebugger.isResponseContainProperty(resJSON, "reason", "breakpoint")) { + threadId = Convert.ToInt32(VSCodeDebugger.GetResponsePropertyValue(resJSON, "threadId")); + return true; + } + return false; + }; + + Assert.True(VSCodeDebugger.IsEventReceived(filter), @"__FILE__:__LINE__"+"\n"+caller_trace); + + StackTraceRequest stackTraceRequest = new StackTraceRequest(); + stackTraceRequest.arguments.threadId = threadId; + stackTraceRequest.arguments.startFrame = 0; + stackTraceRequest.arguments.levels = 20; + var ret = VSCodeDebugger.Request(stackTraceRequest); + Assert.True(ret.Success, @"__FILE__:__LINE__"+"\n"+caller_trace); + + StackTraceResponse stackTraceResponse = + JsonConvert.DeserializeObject(ret.ResponseStr); + var stackFrame = stackTraceResponse.body.stackFrames[0]; + + Assert.Equal(bp_line, stackFrame.line, @"__FILE__:__LINE__"+"\n"+caller_trace); + Assert.Equal(bp_fileName, stackFrame.source.name, @"__FILE__:__LINE__"+"\n"+caller_trace); + Assert.Equal(expectedColumn, stackFrame.column, @"__FILE__:__LINE__"+"\n"+caller_trace); + } + + public void WasBreakpointHitAtStartColumn(string caller_trace, string bpName, int expectedColumn) + { + Func filter = (resJSON) => { + if (VSCodeDebugger.isResponseContainProperty(resJSON, "event", "stopped") + && VSCodeDebugger.isResponseContainProperty(resJSON, "reason", "breakpoint")) { + threadId = Convert.ToInt32(VSCodeDebugger.GetResponsePropertyValue(resJSON, "threadId")); + return true; + } + return false; + }; + + Assert.True(VSCodeDebugger.IsEventReceived(filter), @"__FILE__:__LINE__"+"\n"+caller_trace); + + StackTraceRequest stackTraceRequest = new StackTraceRequest(); + stackTraceRequest.arguments.threadId = threadId; + stackTraceRequest.arguments.startFrame = 0; + stackTraceRequest.arguments.levels = 20; + var ret = VSCodeDebugger.Request(stackTraceRequest); + Assert.True(ret.Success, @"__FILE__:__LINE__"+"\n"+caller_trace); + + Breakpoint breakpoint = ControlInfo.Breakpoints[bpName]; + var lbp = (LineBreakpoint)breakpoint; + StackTraceResponse stackTraceResponse = JsonConvert.DeserializeObject(ret.ResponseStr); + var stackFrame = stackTraceResponse.body.stackFrames[0]; + + Assert.Equal(lbp.NumLine, stackFrame.line, @"__FILE__:__LINE__"+"\n"+caller_trace); + Assert.Equal(lbp.FileName, stackFrame.source.name, @"__FILE__:__LINE__"+"\n"+caller_trace); + Assert.Equal(expectedColumn, stackFrame.column, @"__FILE__:__LINE__"+"\n"+caller_trace); + } + + public void WasBreakpointHitAtColumn(string caller_trace, string bpName, int expectedColumn, int expectedEndColumn) + { + Func filter = (resJSON) => { + if (VSCodeDebugger.isResponseContainProperty(resJSON, "event", "stopped") + && VSCodeDebugger.isResponseContainProperty(resJSON, "reason", "breakpoint")) { + threadId = Convert.ToInt32(VSCodeDebugger.GetResponsePropertyValue(resJSON, "threadId")); + return true; + } + return false; + }; + + Assert.True(VSCodeDebugger.IsEventReceived(filter), @"__FILE__:__LINE__"+"\n"+caller_trace); + + StackTraceRequest stackTraceRequest = new StackTraceRequest(); + stackTraceRequest.arguments.threadId = threadId; + stackTraceRequest.arguments.startFrame = 0; + stackTraceRequest.arguments.levels = 20; + var ret = VSCodeDebugger.Request(stackTraceRequest); + Assert.True(ret.Success, @"__FILE__:__LINE__"+"\n"+caller_trace); + + Breakpoint breakpoint = ControlInfo.Breakpoints[bpName]; + Assert.Equal(BreakpointType.Line, breakpoint.Type, @"__FILE__:__LINE__"+"\n"+caller_trace); + var lbp = (LineBreakpoint)breakpoint; + + StackTraceResponse stackTraceResponse = + JsonConvert.DeserializeObject(ret.ResponseStr); + var stackFrame = stackTraceResponse.body.stackFrames[0]; + + Assert.Equal(lbp.NumLine, stackFrame.line, @"__FILE__:__LINE__"+"\n"+caller_trace); + Assert.Equal(lbp.FileName, stackFrame.source.name, @"__FILE__:__LINE__"+"\n"+caller_trace); + Assert.Equal(expectedColumn, stackFrame.column, @"__FILE__:__LINE__"+"\n"+caller_trace); + Assert.Equal(expectedEndColumn, stackFrame.endColumn, @"__FILE__:__LINE__"+"\n"+caller_trace); + } - public void AddManualBreakpoint(string caller_trace, string bp_fileName, int bp_line) + public void AddManualBreakpoint(string caller_trace, string bp_fileName, int bp_line, int? Column = null) { List listBp; if (!SrcBreakpoints.TryGetValue(bp_fileName, out listBp)) { listBp = new List(); SrcBreakpoints[bp_fileName] = listBp; } - listBp.Add(new SourceBreakpoint(bp_line, null)); + listBp.Add(new SourceBreakpoint(bp_line, null) { column = Column }); List listBpId; if (!SrcBreakpointIds.TryGetValue(bp_fileName, out listBpId)) { @@ -576,15 +673,15 @@ void nested_func11() { void nested_func12() { void nested_func13() { Context Context = (Context)context; Context.WasBreakpointHit(@"__FILE__:__LINE__", "bp23"); - Context.AddManualBreakpoint(@"__FILE__:__LINE__", "Program.cs", 287); // line number with "int test_field = 5;" code - Context.AddManualBreakpoint(@"__FILE__:__LINE__", "Program.cs", 291); // line number with "int i = 5;" code + Context.AddManualBreakpoint(@"__FILE__:__LINE__", "Program.cs", 390); // line number with "int test_field = 5;" code + Context.AddManualBreakpoint(@"__FILE__:__LINE__", "Program.cs", 394); // line number with "int i = 5;" code Context.SetBreakpoints(@"__FILE__:__LINE__"); Context.Continue(@"__FILE__:__LINE__"); - Context.WasManualBreakpointHit(@"__FILE__:__LINE__", "Program.cs", 287); // line number with "int test_field = 5;" code + Context.WasManualBreakpointHit(@"__FILE__:__LINE__", "Program.cs", 390); // line number with "int test_field = 5;" code Context.Continue(@"__FILE__:__LINE__"); - Context.WasManualBreakpointHit(@"__FILE__:__LINE__", "Program.cs", 291); // line number with "int i = 5;" code + Context.WasManualBreakpointHit(@"__FILE__:__LINE__", "Program.cs", 394); // line number with "int i = 5;" code Context.Continue(@"__FILE__:__LINE__"); - Context.WasManualBreakpointHit(@"__FILE__:__LINE__", "Program.cs", 287); // line number with "int test_field = 5;" code + Context.WasManualBreakpointHit(@"__FILE__:__LINE__", "Program.cs", 390); // line number with "int test_field = 5;" code Context.Continue(@"__FILE__:__LINE__"); }); @@ -595,11 +692,70 @@ void nested_func11() { void nested_func12() { void nested_func13() { break; Label.Breakpoint("bp25"); } - Label.Checkpoint("bp_test_not_ordered_line_num", "finish", (Object context) => { + Label.Checkpoint("bp_test_not_ordered_line_num", "bp_test_column", (Object context) => { Context Context = (Context)context; Context.WasBreakpointHit(@"__FILE__:__LINE__", "bp24"); Context.Continue(@"__FILE__:__LINE__"); Context.WasBreakpointHit(@"__FILE__:__LINE__", "bp25"); + + Context.AddBreakpoint(@"__FILE__:__LINE__", "bp_col1", Column: 13); + Context.AddBreakpoint(@"__FILE__:__LINE__", "bp_col2", Column: 28); + Context.AddBreakpoint(@"__FILE__:__LINE__", "bp_col3", Column: 43); + Context.AddBreakpoint(@"__FILE__:__LINE__", "bp_col4", Column: 35); + + int endLine = Context.GetBreakpointLine("multi_end"); + + // Multiline statement: breakpoint set with a column on the non-first line snaps up to the start of the statement. + Context.AddManualBreakpoint(@"__FILE__:__LINE__", "Program.cs", endLine - 1, Column: 5); + + // Multiple breakpoints on the same line coalesce to a single BP at the start of the line, max 1 breakpoint-per line rule + Context.AddBreakpoint(@"__FILE__:__LINE__", "bp_multi_order_A"); + Context.AddBreakpoint(@"__FILE__:__LINE__", "bp_multi_order_A", Column: 17); + Context.AddBreakpoint(@"__FILE__:__LINE__", "bp_multi_order_A", Column: 23); + + Context.AddBreakpoint(@"__FILE__:__LINE__", "bp_multi_order_B", Column: 17); + Context.AddBreakpoint(@"__FILE__:__LINE__", "bp_multi_order_B"); + Context.AddBreakpoint(@"__FILE__:__LINE__", "bp_multi_order_B", Column: 23); + + Context.SetBreakpoints(@"__FILE__:__LINE__"); + Context.Continue(@"__FILE__:__LINE__"); + }); + + int col_a1 = 0;int col_b1 = 5;int col_c1 = 9; Label.Breakpoint("bp_col1"); + int col_a2 = 0;int col_b2 = 5;int col_c2 = 9; Label.Breakpoint("bp_col2"); + int col_a3 = 0;int col_b3 = 5;int col_c3 = 9; Label.Breakpoint("bp_col3"); + int col_a4 = 0;int col_b4 = 5;int col_c4 = 9; Label.Breakpoint("bp_col4"); + + int a = + 1 + 25 + + 12 + 5; int b = 5; Label.Breakpoint("multi_end"); + + int i1 = 0; i1++; i1--; i1 = 5; Label.Breakpoint("bp_multi_order_A"); + int i2 = 0; i2++; i2--; i2 = 5; Label.Breakpoint("bp_multi_order_B"); + + Label.Checkpoint("bp_test_column", "finish", (Object context) => { + Context Context = (Context)context; + int endLine = Context.GetBreakpointLine("multi_end"); + + Context.WasBreakpointHitAtColumn(@"__FILE__:__LINE__", "bp_col1", 13, 28); + Context.Continue(@"__FILE__:__LINE__"); + Context.WasBreakpointHitAtColumn(@"__FILE__:__LINE__", "bp_col2", 28, 43); + Context.Continue(@"__FILE__:__LINE__"); + Context.WasBreakpointHitAtColumn(@"__FILE__:__LINE__", "bp_col3", 43, 58); + Context.Continue(@"__FILE__:__LINE__"); + Context.WasBreakpointHitAtColumn(@"__FILE__:__LINE__", "bp_col4", 28, 43); + Context.Continue(@"__FILE__:__LINE__"); + + // Multiline snap-up: the BP was set on the 2nd line of a 3-line statement and resolves to 'int a =' one line above that + Context.WasManualBreakpointHitAtColumn(@"__FILE__:__LINE__", "Program.cs", endLine - 2, 13); + Context.Continue(@"__FILE__:__LINE__"); + + // Order A: col 0, 17, 23 all coalesce to one BP at start of line + Context.WasBreakpointHitAtStartColumn(@"__FILE__:__LINE__", "bp_multi_order_A", 13); + Context.Continue(@"__FILE__:__LINE__"); + + // Order B: col 17, 0, 23 — order doesn't matter, still one BP at start of line + Context.WasBreakpointHitAtStartColumn(@"__FILE__:__LINE__", "bp_multi_order_B", 13); Context.Continue(@"__FILE__:__LINE__"); });