-
-
Notifications
You must be signed in to change notification settings - Fork 3
🚀 Clear assertion greyed-out lines on restart and highlight issue locations #232
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
🚀 Clear assertion greyed-out lines on restart and highlight issue locations #232
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
🤖 Fix all issues with AI agents
In @python/mqt/debugger/dap/dap_server.py:
- Around line 518-552: The function _build_parse_error_highlight has untested
edge cases and a clarity bug: change the condition currently written as `column
<= 1` to `column == 1` (since column is already clamped to >=1) in the branch
that walks backwards across blank lines, and add unit tests that exercise parse
errors on an empty-first-line file, on a whitespace-only line, and on multiple
consecutive empty lines to assert the returned highlight uses the previous
non-empty line, computes the column correctly (indent start) and sets a
reasonable end_column/snippet/message; target tests at the same behavior covered
by _build_parse_error_highlight so they fail if the branch is incorrect.
📜 Review details
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
📒 Files selected for processing (1)
python/mqt/debugger/dap/dap_server.py
🧰 Additional context used
🧬 Code graph analysis (1)
python/mqt/debugger/dap/dap_server.py (2)
python/mqt/debugger/dap/messages/highlight_error_dap_message.py (2)
HighlightError(22-179)encode(62-70)python/mqt/debugger/dap/messages/output_dap_event.py (2)
encode(54-69)OutputDAPEvent(18-69)
🔇 Additional comments (17)
python/mqt/debugger/dap/dap_server.py (17)
14-14: LGTM: Import additions are appropriate.The
remodule is used for parsing error messages (line 507), andAnyis used for type hints throughout the new methods.Also applies to: 17-17
118-119: LGTM: State fields support the highlight error feature.
pending_highlightsqueues highlight data for deferred emission, and_prevent_exitprevents premature termination when errors are displayed to the user.
172-186: LGTM: Restores normal exit behavior after user interaction.Resetting
_prevent_exitafter stepping or restart commands allows the debugger to exit normally once the user has acknowledged and acted on highlighted errors.
257-261: LGTM: Deferred highlight emission works correctly.Pending highlights (queued by parse errors) are emitted after command processing, and
_prevent_exitis set to ensure the user can view them before the session terminates.
271-275: LGTM: Exit condition properly respects the prevent-exit flag.Adding
not self._prevent_exitensures the debugger remains active when error highlights are displayed, preventing premature session termination.
355-355: LGTM: Appropriate category for error output.Using
"stderr"for assertion failure messages correctly categorizes them as errors rather than normal console output.
357-361: LGTM: Assertion failures now emit structured highlights.The new code collects highlight entries for error causes and emits them as
HighlightErrorevents, enabling rich UI feedback for debugging assertion failures.
431-462: LGTM: Robust highlight collection with fallback.The method correctly collects highlights for error causes and provides a sensible fallback (highlighting the failing instruction) when diagnostics are unavailable or produce no causes. Exception handling at line 443 prevents crashes.
464-486: LGTM: Highlight entry construction is correct.The method carefully distinguishes between:
end_position_exclusive(line 471-474): For UI highlight range, excludes trailing newlines- Snippet extraction (line 476): Includes the character at
end_pos, then strips whitespaceThis ensures precise UI highlighting while capturing the full instruction text for display.
488-495: LGTM: Simple and safe error cause mapping.The method maps error cause types to string identifiers with a safe fallback to
"unknown".
497-502: Verify that only one parse error can occur per launch.Line 502 replaces the entire
pending_highlightslist with a single entry. If multiple parse errors could occur during a single launch/restart, only the last one would be displayed.If multiple parse errors are possible, consider appending instead:
if entry is not None: self.pending_highlights.append(entry)Otherwise, document that only the first/last parse error is shown.
504-516: LGTM: Robust parse error location extraction.The regex correctly parses compiler error format, and the fallback to
(1, 1, error_message)ensures the method never fails even with unexpected error formats.
554-577: LGTM: Robust recursive message flattening.The method correctly handles nested message structures with appropriate filtering of empty strings and None values. The recursive approach cleanly processes dictionaries, lists, and primitive types.
579-606: LGTM: Message hierarchy refactoring preserves functionality.The refactored method correctly extracts message components, flattens nested structures, and delegates to the new
send_message_simplemethod. The addedcategoryparameter is backward compatible with its default value.
607-646: LGTM: New message sending method is clean and focused.The method consolidates message formatting and transmission into a single, straightforward function with clear parameter separation. The early return (line 636) handles empty messages gracefully.
118-119: The grey-out clearing mechanism is correctly implemented, though not through explicit clear events. Instead, grey-outs are implicitly reset by recalculation:
- On restart:
RestartDAPMessage.handle()callsreset_simulation()to clear state, then loads new code- Grey-out reset occurs automatically:
regular_checks()recalculatesgray_out_areasfrom scratch based on the current instruction's dependencies, and sends a freshGrayOutDAPEventwith the updated areas- This happens after every command including Launch and Restart, ensuring old grey-outs from a previous execution are replaced with correct grey-outs for the new state
The implementation correctly clears assertion-related highlights via
pending_highlights(emitted asHighlightErrorevents) which is separate from the grey-out mechanism. No changes needed.Likely an incorrect or invalid review comment.
373-389: The coordinate conversion changes are correct and well-tested.The modifications properly handle edge cases:
- Line 373: Initializes to line 1 for non-empty source
- Line 376: The
<=condition correctly places positions at newlines on the current line (verified bytest_code_pos_to_coordinates_handles_line_end())- Lines 381-384: Fallback for positions extending beyond the file
All improvements are substantiated and the critical boundary condition change is covered by tests.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
🤖 Fix all issues with AI agents
In @src/common/parsing/CodePreprocessing.cpp:
- Around line 163-208: The catch in validateTargets around the std::stoul call
is too broad (catching std::exception); change it to catch the specific
exceptions thrown by std::stoul—catch const std::invalid_argument& and const
std::out_of_range& (either as two catches or a single multi-catch) and in each
handler rethrow the ParsingError as currently done; keep the surrounding logic
and error message intact and do not swallow other exception types.
- Around line 62-76: The loop that counts newlines in LineColumn
lineColumnForOffset should be replaced with std::count for clarity and potential
performance: compute auto newlineCount = std::count(code.begin(), code.begin() +
lineStart, '\n') and set location.line = 1 + newlineCount instead of iterating
up to lineStart; keep the existing calculation of lineStart and location.column
unchanged and ensure to include <algorithm> if not already included.
📜 Review details
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
📒 Files selected for processing (1)
src/common/parsing/CodePreprocessing.cpp
🧰 Additional context used
🧬 Code graph analysis (1)
src/common/parsing/CodePreprocessing.cpp (3)
include/common/parsing/ParsingError.hpp (1)
ParsingError(32-32)src/common/parsing/ParsingError.cpp (1)
ParsingError(23-23)include/common/parsing/CodePreprocessing.hpp (1)
isVariableDeclaration(296-296)
🪛 Cppcheck (2.19.0)
src/common/parsing/CodePreprocessing.cpp
[information] 24-24: Include file
(missingIncludeSystem)
[information] 25-25: Include file
(missingIncludeSystem)
[information] 23-23: Include file
(missingIncludeSystem)
[information] 24-24: Include file
(missingIncludeSystem)
[information] 23-23: Include file
(missingIncludeSystem)
[information] 24-24: Include file
(missingIncludeSystem)
[information] 25-25: Include file
(missingIncludeSystem)
[information] 23-23: Include file
(missingIncludeSystem)
[information] 24-24: Include file
(missingIncludeSystem)
[information] 25-25: Include file
(missingIncludeSystem)
🔇 Additional comments (8)
src/common/parsing/CodePreprocessing.cpp (8)
23-25: LGTM: Includes support new validation logic.The added includes are appropriate for the new digit checking and exception handling functionality.
43-49: LGTM: Robust digit validation.The function correctly validates non-empty digit-only strings with proper casting for
std::isdigit.
85-109: LGTM: Appropriate fallback for error location.The function correctly locates the target token or falls back to the first non-space character, which is reasonable for error reporting.
119-152: LGTM: Clear error formatting utilities.The error formatting follows standard compiler conventions and provides helpful context for users.
509-513: LGTM: Improved error location accuracy.The adjusted
trueStartcalculation correctly identifies the actual start of the instruction by finding the first non-space character, improving error reporting precision.
539-550: LGTM: Robust register validation with clear error messages.The validation correctly checks for empty names and non-digit sizes before parsing. The exception handling is consistent with
validateTargets(lines 189-196), though both could benefit from more specific exception types as noted in that function's review.
615-616: LGTM: Comprehensive assertion validation.The call to
validateTargetsis appropriately placed after unfolding and internal validation, with clear context for error messages.
621-624: LGTM: Appropriate target validation for instructions.The validation correctly skips variable declarations (which have dedicated handling at lines 534-552) and validates targets for all other instructions.
| void validateTargets(const std::string& code, size_t instructionStart, | ||
| const std::vector<std::string>& targets, | ||
| const std::map<std::string, size_t>& definedRegisters, | ||
| const std::vector<std::string>& shadowedRegisters, | ||
| const std::string& context) { | ||
| for (const auto& target : targets) { | ||
| if (target.empty()) { | ||
| continue; | ||
| } | ||
| const auto open = target.find('['); | ||
| if (open == std::string::npos) { | ||
| continue; | ||
| } | ||
| const auto close = target.find(']', open + 1); | ||
| if (open == 0 || close == std::string::npos || close != target.size() - 1) { | ||
| throw ParsingError(formatParseError(code, instructionStart, | ||
| invalidTargetDetail(target, context), | ||
| target)); | ||
| } | ||
| const auto registerName = target.substr(0, open); | ||
| const auto indexText = target.substr(open + 1, close - open - 1); | ||
| if (!isDigits(indexText)) { | ||
| throw ParsingError(formatParseError(code, instructionStart, | ||
| invalidTargetDetail(target, context), | ||
| target)); | ||
| } | ||
| size_t registerIndex = 0; | ||
| try { | ||
| registerIndex = std::stoul(indexText); | ||
| } catch (const std::exception&) { | ||
| throw ParsingError(formatParseError(code, instructionStart, | ||
| invalidTargetDetail(target, context), | ||
| target)); | ||
| } | ||
| if (std::ranges::find(shadowedRegisters, registerName) != | ||
| shadowedRegisters.end()) { | ||
| continue; | ||
| } | ||
| const auto found = definedRegisters.find(registerName); | ||
| if (found == definedRegisters.end() || found->second <= registerIndex) { | ||
| throw ParsingError(formatParseError(code, instructionStart, | ||
| invalidTargetDetail(target, context), | ||
| target)); | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick | 🔵 Trivial
Consider more specific exception catching.
The catch block at line 192 catches std::exception, which is quite broad. std::stoul specifically throws std::invalid_argument and std::out_of_range. While catching the base class is safe, being more specific improves code clarity.
♻️ Proposed refactor
size_t registerIndex = 0;
try {
registerIndex = std::stoul(indexText);
- } catch (const std::exception&) {
+ } catch (const std::invalid_argument&) {
+ throw ParsingError(formatParseError(code, instructionStart,
+ invalidTargetDetail(target, context),
+ target));
+ } catch (const std::out_of_range&) {
throw ParsingError(formatParseError(code, instructionStart,
invalidTargetDetail(target, context),
target));
}🤖 Prompt for AI Agents
In @src/common/parsing/CodePreprocessing.cpp around lines 163 - 208, The catch
in validateTargets around the std::stoul call is too broad (catching
std::exception); change it to catch the specific exceptions thrown by
std::stoul—catch const std::invalid_argument& and const std::out_of_range&
(either as two catches or a single multi-catch) and in each handler rethrow the
ParsingError as currently done; keep the surrounding logic and error message
intact and do not swallow other exception types.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 5
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (4)
python/mqt/debugger/dap/dap_server.py (2)
579-646:send_message_hierarchy/send_message_simplerefactor looks good; adjust docstrings + avoidstr(None)title.
Currentlytitle = str(message.get("title", ""))can produce"None".Proposed tweak
@@ - title = str(message.get("title", "")) + raw_title = message.get("title") + title = raw_title if isinstance(raw_title, str) else "" self.send_message_simple(title, body, end, line, column, connection, category)
412-463: Handle unhandledErrorCauseTypevalues with a default message instead of returning empty string.The
format_error_cause()method uses a nested ternary operator that returns""for anyErrorCauseTypenot explicitly handled (currently onlyMissingInteractionandControlAlwaysZeroare covered). If mqt-core adds newErrorCauseTypevariants in the future, those would be silently formatted as empty messages, potentially violating DAP protocol message constraints.Proposed fix
@@ for cause in error_causes: - message = self.format_error_cause(cause) + message = self.format_error_cause(cause).strip() + if not message: + # Keep HighlightError payload valid even when we don't have a dedicated formatter yet. + message = "Potential error cause at this instruction." reason = self._format_highlight_reason(cause.type) entry = self._build_highlight_entry(cause.instruction, reason, message) if entry is not None: highlights.append(entry)Also applies to: 464-487
python/mqt/debugger/dap/messages/restart_dap_message.py (1)
57-76: Fix NameError (parsed_successfullyis undefined) and handleload_codeRuntimeError as non-fatal (align with Launch).
RestartDAPMessage.handle()on line 73 references undefined variableparsed_successfully, causing aNameErrorat runtime. Additionally, unlikeLaunchDAPMessage, the restart handler doesn't catchRuntimeErrorfromload_code()(line 70) and will crash on parse failures. The response also incorrectly returns"command": "launch"instead of"restart"(line 80).Wrap
load_code()in try-except, defineparsed_successfully, return proper error response on parse failure, and fix the response command field to"restart".Proposed fix
@@ def handle(self, server: DAPServer) -> dict[str, Any]: @@ server.simulation_state.reset_simulation() program_path = Path(self.program) code = program_path.read_text(encoding=locale.getpreferredencoding(False)) server.source_code = code - server.simulation_state.load_code(code) + server.source_file = {"name": program_path.name, "path": str(program_path)} + parsed_successfully = True + try: + parsed_successfully = bool(server.simulation_state.load_code(code)) + except RuntimeError as exc: + parsed_successfully = False + server.queue_parse_error(str(exc)) + return { + "type": "response", + "request_seq": self.sequence_number, + "success": False, + "command": "restart", + "message": "An error occurred while parsing the code.", + } if not self.stop_on_entry: server.simulation_state.run_simulation() if not parsed_successfully: with contextlib.suppress(RuntimeError): server.simulation_state.reset_simulation() + return { + "type": "response", + "request_seq": self.sequence_number, + "success": True, + "command": "restart", + }python/mqt/debugger/dap/messages/launch_dap_message.py (1)
66-83: Fix undefined variableparsed_successfullyon line 81 and ensureserver.source_fileis set.Line 81 references
parsed_successfullywhich is never defined, causing aNameErrorat runtime. Additionally,server.source_fileis not assigned in thehandle()method, breaking downstream calls toHighlightError(...)inDAPServer(which requiressourcewith"name"and"path"keys per line 157-159 ofhighlight_error_dap_message.py).Set
server.source_fileto{"name": program_path.name, "path": str(program_path)}after line 68, and initializeparsed_successfully = Truebefore the try block (or remove the unused check on line 81-83 if it serves no purpose given that exceptions return early on line 72).
🤖 Fix all issues with AI agents
In @python/mqt/debugger/dap/dap_server.py:
- Around line 497-552: The parse error detail can be empty causing
HighlightError.validate() to fail; update _parse_error_location(error_message)
to return a non-empty fallback (e.g., "parse error" or "unknown parse error")
when detail is empty or only whitespace, and ensure
queue_parse_error/_build_parse_error_highlight will propagate that non-empty
string (alternatively also guard in _build_parse_error_highlight by replacing an
empty detail with the same fallback) so the "message" field in the highlight
dict is never empty.
In @python/mqt/debugger/dap/messages/highlight_error_dap_message.py:
- Around line 111-137: The _normalize_position signature says it accepts
Mapping[str, Any] | None but the implementation doesn't handle None; either
remove | None from the type hint on _normalize_position to disallow None, or
explicitly handle None at the start of the function (e.g., if position is None:
raise TypeError("Highlight positions must be mappings with 'line' and
'column'.") ) so the runtime behavior matches the signature; update the function
annotation or add the explicit None check inside _normalize_position
accordingly.
In @src/backend/dd/DDSimDebug.cpp:
- Line 1153: In ddsimRunAll, the local pointer 'ddsim' assigned via
toDDSimulationState(self) is never used; either remove the unused declaration
entirely or, if you plan to use it later, add a clear comment above it
explaining it's intentionally unused or explicitly mark it as used (e.g.,
(void)ddsim) to avoid warnings; update the code around the 'ddsim' declaration
accordingly.
In @test/python/test_dap_server.py:
- Around line 28-44: The fake diagnostics object uses
potential_error_causes=list (the type) which is confusing; change
fake_diagnostics to set potential_error_causes to an actual empty list (e.g.,
potential_error_causes=[]) so the
test_build_highlight_entry_does_not_span_next_instruction more clearly supplies
a diagnostics object with no causes when server.simulation_state.get_diagnostics
is used by DAPServer.collect_highlight_entries.
📜 Review details
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
📒 Files selected for processing (12)
bindings/InterfaceBindings.cppinclude/backend/dd/DDSimDebug.hppinclude/backend/debug.hpython/mqt/debugger/dap/dap_server.pypython/mqt/debugger/dap/messages/__init__.pypython/mqt/debugger/dap/messages/highlight_error_dap_message.pypython/mqt/debugger/dap/messages/launch_dap_message.pypython/mqt/debugger/dap/messages/restart_dap_message.pysrc/backend/dd/DDSimDebug.cppsrc/common/parsing/CodePreprocessing.cppsrc/frontend/cli/CliFrontEnd.cpptest/python/test_dap_server.py
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-11-27T08:52:06.428Z
Learnt from: lsschmid
Repo: munich-quantum-toolkit/qmap PR: 832
File: src/hybridmap/HybridNeutralAtomMapper.cpp:1379-1383
Timestamp: 2025-11-27T08:52:06.428Z
Learning: In src/hybridmap/HybridNeutralAtomMapper.cpp, the getBestMovePos function throws std::runtime_error when no move position is found (finalBestPos.coords is empty). This behavior is acceptable for current use cases since tight architectures are not a concern. A future improvement would be to fall back to SWAP-based strategies instead of throwing.
Applied to files:
src/common/parsing/CodePreprocessing.cppsrc/backend/dd/DDSimDebug.cpp
📚 Learning: 2025-11-05T07:42:45.507Z
Learnt from: burgholzer
Repo: munich-quantum-toolkit/core PR: 1287
File: test/qdmi/dd/error_handling_test.cpp:118-194
Timestamp: 2025-11-05T07:42:45.507Z
Learning: In the munich-quantum-toolkit/core QDMI device API, session parameters can only be set before calling `device_session_init()`. Once a session is initialized, any attempt to set a parameter returns `QDMI_ERROR_BADSTATE`. Since `SessionGuard` (in test/qdmi/dd/helpers/test_utils.hpp) automatically initializes the session in its constructor, tests that need to verify session parameter setting behavior before initialization must allocate a separate uninitialized session rather than reusing the `SessionGuard`'s session.
Applied to files:
src/backend/dd/DDSimDebug.cpp
🧬 Code graph analysis (8)
bindings/InterfaceBindings.cpp (2)
include/frontend/cli/CliFrontEnd.hpp (2)
code(46-46)code(68-68)test/utils/common_fixtures.hpp (6)
code(211-215)code(211-213)code(236-244)code(236-237)code(254-256)code(254-254)
python/mqt/debugger/dap/messages/__init__.py (1)
python/mqt/debugger/dap/messages/highlight_error_dap_message.py (1)
HighlightError(22-179)
python/mqt/debugger/dap/messages/restart_dap_message.py (1)
python/mqt/debugger/pydebugger.pyi (1)
reset_simulation(193-198)
python/mqt/debugger/dap/messages/launch_dap_message.py (1)
python/mqt/debugger/pydebugger.pyi (1)
reset_simulation(193-198)
src/common/parsing/CodePreprocessing.cpp (3)
include/common/parsing/ParsingError.hpp (1)
ParsingError(32-32)src/common/parsing/ParsingError.cpp (1)
ParsingError(23-23)include/common/parsing/CodePreprocessing.hpp (1)
isVariableDeclaration(296-296)
test/python/test_dap_server.py (2)
python/mqt/debugger/dap/dap_server.py (2)
code_pos_to_coordinates(363-389)collect_highlight_entries(431-462)python/mqt/debugger/pydebugger.pyi (3)
potential_error_causes(527-535)get_instruction_position(274-284)get_diagnostics(419-424)
src/backend/dd/DDSimDebug.cpp (1)
include/backend/dd/DDSimDebug.hpp (1)
preprocessAssertionCode(678-678)
python/mqt/debugger/dap/dap_server.py (3)
python/mqt/debugger/dap/messages/highlight_error_dap_message.py (2)
HighlightError(22-179)encode(62-70)python/mqt/debugger/dap/messages/output_dap_event.py (2)
encode(54-69)OutputDAPEvent(18-69)python/mqt/debugger/pydebugger.pyi (4)
ErrorCause(436-445)get_diagnostics(419-424)get_instruction_position(274-284)ErrorCauseType(25-33)
🪛 Cppcheck (2.19.0)
src/common/parsing/CodePreprocessing.cpp
[information] 24-24: Include file
(missingIncludeSystem)
[information] 25-25: Include file
(missingIncludeSystem)
[information] 23-23: Include file
(missingIncludeSystem)
[information] 24-24: Include file
(missingIncludeSystem)
[information] 23-23: Include file
(missingIncludeSystem)
[information] 24-24: Include file
(missingIncludeSystem)
[information] 25-25: Include file
(missingIncludeSystem)
[information] 23-23: Include file
(missingIncludeSystem)
[information] 24-24: Include file
(missingIncludeSystem)
[information] 25-25: Include file
(missingIncludeSystem)
🪛 Ruff (0.14.10)
python/mqt/debugger/dap/messages/restart_dap_message.py
73-73: Undefined name parsed_successfully
(F821)
python/mqt/debugger/dap/messages/launch_dap_message.py
81-81: Undefined name parsed_successfully
(F821)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (15)
- GitHub Check: 🐍 Test (ubuntu-24.04-arm) / 🐍 ubuntu-24.04-arm
- GitHub Check: 🇨 Test 🍎 (macos-14, clang, Release) / 🍎 macos-14 clang Release
- GitHub Check: 🇨 Test 🍎 (macos-15-intel, clang, Release) / 🍎 macos-15-intel clang Release
- GitHub Check: 🐍 Test (windows-2022) / 🐍 windows-2022
- GitHub Check: 🇨 Test 🍎 (macos-14, clang, Debug) / 🍎 macos-14 clang Debug
- GitHub Check: 🐍 Test (ubuntu-24.04) / 🐍 ubuntu-24.04
- GitHub Check: 🇨 Test 🏁 (windows-2022, msvc, Release) / 🏁 windows-2022 msvc Release
- GitHub Check: 🐍 Test (macos-15-intel) / 🐍 macos-15-intel
- GitHub Check: 🐍 Test (macos-14) / 🐍 macos-14
- GitHub Check: 🇨 Test 🐧 (ubuntu-24.04-arm, gcc, Release) / 🐧 ubuntu-24.04-arm gcc Release
- GitHub Check: 🇨 Test 🐧 (ubuntu-24.04, gcc, Debug) / 🐧 ubuntu-24.04 gcc Debug
- GitHub Check: 🇨 Test 🐧 (ubuntu-24.04, gcc, Release) / 🐧 ubuntu-24.04 gcc Release
- GitHub Check: 🇨 Test 🏁 (windows-2022, msvc, Debug) / 🏁 windows-2022 msvc Debug
- GitHub Check: 🇨 Test 🏁 (windows-11-arm, msvc, Release) / 🏁 windows-11-arm msvc Release
- GitHub Check: 🇨 Lint / 🚨 Lint
🔇 Additional comments (24)
src/common/parsing/CodePreprocessing.cpp (11)
23-25: LGTM!The new includes are appropriate for the added functionality:
<cctype>forstd::isdigitand<exception>for the exception base class used in catch handlers.
43-49: LGTM!Good defensive implementation. The cast to
unsigned charbefore callingstd::isdigitis the correct approach to avoid undefined behavior with negative char values on platforms wherecharis signed.
62-76: LGTM!The line/column calculation logic is correct. The use of
rfindto find the start of the current line and the subsequent iteration to count newlines handles edge cases properly.
85-109: LGTM!The function correctly locates targets within a line and provides a sensible fallback to the first non-whitespace column when the target isn't found. This improves error message precision.
119-125: LGTM!The error format follows the standard compiler diagnostic style (
<input>:line:column: message), which enables IDE integration for clickable error locations.
133-152: LGTM!The helper functions provide consistent error message formatting for target and register validation errors.
163-208: LGTM!The validation function is comprehensive and correctly handles:
- Bracket syntax validation
- Numeric index validation with overflow protection via
isDigits+std::stoulwith try/catch- Shadowed register bypass for function parameters
- Bounds checking against defined register sizes
The belt-and-suspenders approach of checking
isDigitsbeforestd::stoulis appropriate sincestoulcan still throw on overflow for very large numbers.
509-513: LGTM!Good improvement to skip leading whitespace when computing
trueStart, resulting in more accurate error locations that point to actual code rather than indentation.
539-551: LGTM!Robust parsing with proper validation. The
isDigitscheck followed bystd::stoulwith exception handling protects against both non-numeric input and integer overflow, providing clear error messages in both cases.
615-616: LGTM!The validation order is correct: first unfold register references to individual qubits, then perform semantic validation via
a->validate(), and finally validate target references against defined registers.
621-624: LGTM!Correctly skips validation for variable declarations (which define registers rather than reference them) while validating targets for all other instruction types.
src/frontend/cli/CliFrontEnd.cpp (1)
72-80: LGTM! Improved error reporting for code loading failures.The implementation correctly checks for the presence of the
getLastErrorMessagefunction pointer before calling it, and provides a helpful fallback message when no detailed error is available. This significantly improves the debugging experience when code loading fails.include/backend/debug.h (1)
57-66: LGTM! Well-documented error retrieval API.The new
getLastErrorMessageinterface method is clearly documented with appropriate lifetime semantics. The placement afterloadCodeis logical, and the ownership model (implementation-owned pointer) is clearly stated.bindings/InterfaceBindings.cpp (1)
177-187: LGTM! Enhanced Python binding error messages.The error handling correctly captures the result, queries for a detailed error message when available, and provides an appropriate fallback. This brings the Python bindings in line with the improved error reporting available in other interfaces.
include/backend/dd/DDSimDebug.hpp (1)
122-125: LGTM! Storage for error messages added.The addition of the
lastErrorMessagemember provides the necessary storage to implement the error retrieval interface. Usingstd::stringis appropriate for managing the lifetime and memory of error messages.src/backend/dd/DDSimDebug.cpp (5)
78-84: LGTM! Correct implementation of error message retrieval.The function correctly returns
nullptrwhen no error message is available and otherwise returns the C-string from the ownedstd::string. The lifetime semantics are safe since the string is owned by theDDSimulationStateinstance.
524-524: LGTM! Interface wiring is correct.The assignment of
ddsimGetLastErrorMessageto the interface function pointer follows the established pattern for other interface methods.
584-584: LGTM! Error state cleared during initialization.Clearing
lastErrorMessageduring initialization ensures a clean state for each session.
603-635: LGTM! Comprehensive error handling with proper state management.The changes correctly:
- Set
ready = falseearly to prevent use of partially-loaded state- Clear all state containers including
lastErrorMessagefor a clean slate- Capture exception messages from both specific and generic exceptions
- Provide appropriate fallback messages when exception details are unavailable
- Return
ERRORafter recording the error messageThe error handling is robust and will significantly improve the debugging experience.
1424-1428: This breakpoint snapping logic is intentional and correct, but verify if this code was newly added in the current PR.The logic at lines 1424-1428 implements standard debugger behavior: when a breakpoint is requested at a position before an instruction starts, it snaps to that instruction. This is consistent with the documented behavior stating "MQT Debugger will then determine the instruction that corresponds to this location in the code" and is confirmed by the DAP implementation which returns the actual instruction position to the client.
However, if this is newly added code, confirm it's related to the PR's objectives. If pre-existing, this review comment can be resolved.
python/mqt/debugger/dap/messages/__init__.py (1)
24-62: Export ofHighlightErrorlooks consistent with existing package API patterns.test/python/test_dap_server.py (1)
18-25: Good regression coverage for newline boundary (pos at\n) incode_pos_to_coordinates.python/mqt/debugger/dap/messages/highlight_error_dap_message.py (1)
22-70: Solid, defensive event payload normalization + stablebodyshape (highlights/source).python/mqt/debugger/dap/dap_server.py (1)
271-275: Verify the intended DAP lifecycle when highlights are shown: The_prevent_exitflag suppresses theExitedevent when set toTrue(after sending highlights) and is only reset upon specific client commands (execution/restart). If the client closes the connection without issuing another command after highlights are shown, theExitedevent will never be sent, which may violate DAP protocol expectations. Confirm whether this is intentional for UX purposes or if the flag should be reset differently (e.g., after highlights are acknowledged by the client, or on disconnect).
| self.pending_highlights: list[dict[str, Any]] = [] | ||
| self._prevent_exit = False | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
# First, let's look at the file structure and locate the relevant code
cd python/mqt/debugger/dap
ls -laRepository: munich-quantum-toolkit/debugger
Length of output: 434
🏁 Script executed:
# Check lines 118-120 in dap_server.py (initialization section)
head -130 dap_server.py | tail -20Repository: munich-quantum-toolkit/debugger
Length of output: 147
🏁 Script executed:
# Check lines 257-261 in dap_server.py (highlight emission section)
sed -n '250,270p' dap_server.pyRepository: munich-quantum-toolkit/debugger
Length of output: 131
🏁 Script executed:
# Search for source_file initialization/assignments
rg -n 'source_file\s*=' dap_server.py -A 2 -B 2Repository: munich-quantum-toolkit/debugger
Length of output: 128
🏁 Script executed:
# Check HighlightError class definition and validate method
rg -n 'class HighlightError\|def validate' messages.py -A 5Repository: munich-quantum-toolkit/debugger
Length of output: 126
🏁 Script executed:
# Check DAPEvent class and encode method
rg -n 'class DAPEvent\|def encode' messages.py -A 3Repository: munich-quantum-toolkit/debugger
Length of output: 126
🏁 Script executed:
# Check lines 118-120 in dap_server.py (initialization section)
sed -n '110,130p' python/mqt/debugger/dap/dap_server.pyRepository: munich-quantum-toolkit/debugger
Length of output: 902
🏁 Script executed:
# Check lines 257-261 in dap_server.py (highlight emission section)
sed -n '250,270p' python/mqt/debugger/dap/dap_server.pyRepository: munich-quantum-toolkit/debugger
Length of output: 1141
🏁 Script executed:
# Search for source_file initialization/assignments
rg -n 'source_file\s*=' python/mqt/debugger/dap/dap_server.py -A 2 -B 2Repository: munich-quantum-toolkit/debugger
Length of output: 57
🏁 Script executed:
# Check HighlightError class definition in messages
rg -n 'class HighlightError' python/mqt/debugger/dap/messages -A 20Repository: munich-quantum-toolkit/debugger
Length of output: 2336
🏁 Script executed:
# Check DAPEvent class and encode method
rg -n 'class DAPEvent' python/mqt/debugger/dap/messages -A 10Repository: munich-quantum-toolkit/debugger
Length of output: 865
🏁 Script executed:
# Look for validate method in HighlightError or its parent
rg -n 'def validate' python/mqt/debugger/dap/messages -B 2 -A 10Repository: munich-quantum-toolkit/debugger
Length of output: 39056
🏁 Script executed:
# Get the complete HighlightError.validate() method
sed -n '47,75p' python/mqt/debugger/dap/messages/highlight_error_dap_message.pyRepository: munich-quantum-toolkit/debugger
Length of output: 1206
🏁 Script executed:
# Search for where source_file is assigned in dap_server.py
rg -n 'self\.source_file' python/mqt/debugger/dap/dap_server.py -B 2 -A 2Repository: munich-quantum-toolkit/debugger
Length of output: 1217
🏁 Script executed:
# Check if source_file is set in launch or initialize handlers
rg -n 'source_file' python/mqt/debugger/dap/messages -B 1 -A 1 | head -50Repository: munich-quantum-toolkit/debugger
Length of output: 1324
🏁 Script executed:
# Search for where source_file is assigned (initialization)
rg -n 'source_file\s*=' python/mqt/debugger/dap/dap_server.pyRepository: munich-quantum-toolkit/debugger
Length of output: 57
🏁 Script executed:
# Check the full context around line 258-259 and see if source_file is checked before use
sed -n '240,270p' python/mqt/debugger/dap/dap_server.pyRepository: munich-quantum-toolkit/debugger
Length of output: 1752
🏁 Script executed:
# Check in messages/set_breakpoints_dap_message.py where source_file is accessed
sed -n '45,75p' python/mqt/debugger/dap/messages/set_breakpoints_dap_message.pyRepository: munich-quantum-toolkit/debugger
Length of output: 1477
🏁 Script executed:
# Search for initialization in launch or other startup handlers
rg -n 'self.source_file\s*=' python/mqt/debugger/dap/messages/ -B 2 -A 2Repository: munich-quantum-toolkit/debugger
Length of output: 57
🏁 Script executed:
# Search more broadly for where source_file might be set in the entire dap module
rg -n 'source_file' python/mqt/debugger/dap/dap_server.pyRepository: munich-quantum-toolkit/debugger
Length of output: 469
🏁 Script executed:
# Check the LaunchDAPMessage or InitializeDAPMessage handlers to see if they set source_file
rg -n 'def handle' python/mqt/debugger/dap/messages/launch_dap_message.py -A 30 | head -50Repository: munich-quantum-toolkit/debugger
Length of output: 1354
🏁 Script executed:
# Search for where source_file gets populated
rg -n 'source_file.*:' python/mqt/debugger/dap/dap_server.py -B 3 -A 3Repository: munich-quantum-toolkit/debugger
Length of output: 277
🏁 Script executed:
# Look for where source_file is set - check the full __init__ and other methods
sed -n '100,200p' python/mqt/debugger/dap/dap_server.pyRepository: munich-quantum-toolkit/debugger
Length of output: 4151
🏁 Script executed:
# Check the initialize handler which likely sets source_file
rg -n 'def handle' python/mqt/debugger/dap/messages/initialize_dap_message.py -A 40 | head -60Repository: munich-quantum-toolkit/debugger
Length of output: 859
🏁 Script executed:
# Search for initialization of source_file attribute
rg -n 'server\.source_file\s*=' python/mqt/debugger/dap/messages -B 2 -A 2Repository: munich-quantum-toolkit/debugger
Length of output: 57
🏁 Script executed:
# Let's check if source_file is ever populated anywhere - search entire dap module
rg -n 'source_file\s*=' python/mqt/debugger/dapRepository: munich-quantum-toolkit/debugger
Length of output: 57
🏁 Script executed:
# Look at the complete launch_dap_message handle method
sed -n '57,95p' python/mqt/debugger/dap/messages/launch_dap_message.pyRepository: munich-quantum-toolkit/debugger
Length of output: 1304
🏁 Script executed:
# Check if maybe source_file is set in LaunchDAPMessage's handle or elsewhere
cat python/mqt/debugger/dap/messages/launch_dap_message.pyRepository: munich-quantum-toolkit/debugger
Length of output: 2889
🏁 Script executed:
# Search across the entire codebase for any assignment to source_file
rg -rn 'source_file.*=' python/mqt/debugger/Repository: munich-quantum-toolkit/debugger
Length of output: 209
🏁 Script executed:
# Check the complete HighlightError.__init__ to see how it normalizes source
sed -n '36,50p' python/mqt/debugger/dap/messages/highlight_error_dap_message.pyRepository: munich-quantum-toolkit/debugger
Length of output: 741
🏁 Script executed:
# Let's check the _normalize_highlight and related methods in HighlightError
sed -n '60,100p' python/mqt/debugger/dap/messages/highlight_error_dap_message.pyRepository: munich-quantum-toolkit/debugger
Length of output: 1692
🏁 Script executed:
# Check the _normalize_source method
sed -n '100,140p' python/mqt/debugger/dap/messages/highlight_error_dap_message.pyRepository: munich-quantum-toolkit/debugger
Length of output: 1625
🏁 Script executed:
# Check what happens when source_file is accessed - let's trace initialization flow
grep -n 'def __init__' python/mqt/debugger/dap/dap_server.py -A 20Repository: munich-quantum-toolkit/debugger
Length of output: 1038
🏁 Script executed:
# Search if source_file is perhaps initialized later in a superclass or elsewhere
rg -n 'class DAPServer' python/mqt/debugger/dap/dap_server.py -A 50 | head -80Repository: munich-quantum-toolkit/debugger
Length of output: 1987
🏁 Script executed:
# Check the complete _normalize_source method
sed -n '140,180p' python/mqt/debugger/dap/messages/highlight_error_dap_message.pyRepository: munich-quantum-toolkit/debugger
Length of output: 1724
🏁 Script executed:
# Let's trace where source_file might be set - check all files in messages folder
find python/mqt/debugger/dap/messages -name '*.py' -exec grep -l 'source_file' {} \;Repository: munich-quantum-toolkit/debugger
Length of output: 253
🏁 Script executed:
# Search to see if source_file is set in InitializeDAPMessage or anywhere before highlight emission
rg -n 'source_file' python/mqt/debugger/dap/messages/initialize_dap_message.py -B 2 -A 2Repository: munich-quantum-toolkit/debugger
Length of output: 57
🏁 Script executed:
# Check if there's an exception handler around the highlight emission code
sed -n '245,275p' python/mqt/debugger/dap/dap_server.pyRepository: munich-quantum-toolkit/debugger
Length of output: 1618
🏁 Script executed:
# Look for all usages of self.source_file to see the pattern
rg -n 'self\.source_file' python/mqt/debugger/dap/dap_server.py -B 1 -A 1Repository: munich-quantum-toolkit/debugger
Length of output: 777
🏁 Script executed:
# Check restart_dap_message to see if source_file is set there
cat python/mqt/debugger/dap/messages/restart_dap_message.pyRepository: munich-quantum-toolkit/debugger
Length of output: 2673
🏁 Script executed:
# Let's check the entire flow - does any handler set server.source_file?
rg -rn 'server\.source' python/mqt/debugger/dap/messages/ | grep -v '.pyc'Repository: munich-quantum-toolkit/debugger
Length of output: 1054
🏁 Script executed:
# Check if maybe source_file gets populated in __init__ or somewhere we haven't seen
rg -n '\["name"\].*\["path"\]' python/mqt/debugger/dap -B 3 -A 1Repository: munich-quantum-toolkit/debugger
Length of output: 660
🏁 Script executed:
# Let's verify - are there any tests that show how source_file should be initialized?
find . -name '*test*.py' -path '*/debugger/*' | head -5Repository: munich-quantum-toolkit/debugger
Length of output: 57
🏁 Script executed:
# Check if there's a way source_file gets populated that we're missing
rg -n 'source_file\s*:' python/mqt/debugger/dap/dap_server.py -B 5 -A 15Repository: munich-quantum-toolkit/debugger
Length of output: 765
🏁 Script executed:
# Let's verify the actual crash scenario - access self.source_file when it's not set
python3 << 'EOF'
# Simulate what happens when accessing an uninitialized attribute
class DAPServer:
host: str
source_file: dict # Type annotation only, no initialization
def __init__(self):
self.host = "127.0.0.1"
# source_file is NOT initialized here
try:
server = DAPServer()
print(f"Trying to access source_file: {server.source_file}")
except AttributeError as e:
print(f"ERROR: {e}")
EOFRepository: munich-quantum-toolkit/debugger
Length of output: 161
Initialize source_file and add exception handling for highlight emission to prevent server crashes.
source_file is declared but never initialized in __init__, so accessing self.source_file at line 258 will raise AttributeError. Even if initialized, HighlightError.__init__ calls _normalize_source() which validates that source has required 'name' and 'path' keys (raises TypeError/ValueError if missing), and validate() requires each highlight to have a non-empty 'message' field. Any of these exceptions will crash the debug session because there is no exception handling around the highlight emission.
Wrap highlight creation and emission in try/except to catch TypeError and ValueError, and use finally to clear pending_highlights even on failure:
Proposed fix
self.columns_start_at_one = True
self.pending_highlights: list[dict[str, Any]] = []
self._prevent_exit = False
+ self.source_file = {"name": "", "path": ""} if self.pending_highlights:
- highlight_event = mqt.debugger.dap.messages.HighlightError(self.pending_highlights, self.source_file)
- send_message(json.dumps(highlight_event.encode()), connection)
- self.pending_highlights = []
- self._prevent_exit = True
+ try:
+ highlight_event = mqt.debugger.dap.messages.HighlightError(self.pending_highlights, self.source_file)
+ send_message(json.dumps(highlight_event.encode()), connection)
+ self._prevent_exit = True
+ except (TypeError, ValueError):
+ pass
+ finally:
+ self.pending_highlights = []Also applies to: 327, 359
| def test_build_highlight_entry_does_not_span_next_instruction() -> None: | ||
| """Ensure highlight ranges stop at the end of the instruction.""" | ||
| server = DAPServer() | ||
| server.source_code = "measure q[0] -> c[0];\nmeasure q[1] -> c[1];\n" | ||
| first_line_end = server.source_code.index("\n") | ||
| fake_diagnostics = SimpleNamespace(potential_error_causes=list) | ||
| fake_state = SimpleNamespace( | ||
| get_instruction_position=lambda _instr: (0, first_line_end), | ||
| get_diagnostics=lambda: fake_diagnostics, | ||
| ) | ||
| server.simulation_state = fake_state # type: ignore[assignment] | ||
|
|
||
| entries = server.collect_highlight_entries(0) | ||
| assert entries | ||
| entry = entries[0] | ||
| assert entry["range"]["start"]["line"] == 1 | ||
| assert entry["range"]["end"]["line"] == 1 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Test intent is solid; consider making potential_error_causes explicitly return [] for readability.
Optional tweak
- fake_diagnostics = SimpleNamespace(potential_error_causes=list)
+ fake_diagnostics = SimpleNamespace(potential_error_causes=lambda: [])📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| def test_build_highlight_entry_does_not_span_next_instruction() -> None: | |
| """Ensure highlight ranges stop at the end of the instruction.""" | |
| server = DAPServer() | |
| server.source_code = "measure q[0] -> c[0];\nmeasure q[1] -> c[1];\n" | |
| first_line_end = server.source_code.index("\n") | |
| fake_diagnostics = SimpleNamespace(potential_error_causes=list) | |
| fake_state = SimpleNamespace( | |
| get_instruction_position=lambda _instr: (0, first_line_end), | |
| get_diagnostics=lambda: fake_diagnostics, | |
| ) | |
| server.simulation_state = fake_state # type: ignore[assignment] | |
| entries = server.collect_highlight_entries(0) | |
| assert entries | |
| entry = entries[0] | |
| assert entry["range"]["start"]["line"] == 1 | |
| assert entry["range"]["end"]["line"] == 1 | |
| def test_build_highlight_entry_does_not_span_next_instruction() -> None: | |
| """Ensure highlight ranges stop at the end of the instruction.""" | |
| server = DAPServer() | |
| server.source_code = "measure q[0] -> c[0];\nmeasure q[1] -> c[1];\n" | |
| first_line_end = server.source_code.index("\n") | |
| fake_diagnostics = SimpleNamespace(potential_error_causes=lambda: []) | |
| fake_state = SimpleNamespace( | |
| get_instruction_position=lambda _instr: (0, first_line_end), | |
| get_diagnostics=lambda: fake_diagnostics, | |
| ) | |
| server.simulation_state = fake_state # type: ignore[assignment] | |
| entries = server.collect_highlight_entries(0) | |
| assert entries | |
| entry = entries[0] | |
| assert entry["range"]["start"]["line"] == 1 | |
| assert entry["range"]["end"]["line"] == 1 |
🤖 Prompt for AI Agents
In @test/python/test_dap_server.py around lines 28 - 44, The fake diagnostics
object uses potential_error_causes=list (the type) which is confusing; change
fake_diagnostics to set potential_error_causes to an actual empty list (e.g.,
potential_error_causes=[]) so the
test_build_highlight_entry_does_not_span_next_instruction more clearly supplies
a diagnostics object with no causes when server.simulation_state.get_diagnostics
is used by DAPServer.collect_highlight_entries.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
python/mqt/debugger/dap/dap_server.py (1)
373-389: Verify edge case: empty file withlines_start_at_one=False.The refactored logic handles most cases well, but there's a potential edge case:
- When
linesis empty andlines_start_at_one=False, the function would return(-1, col)after the adjustment on line 388.- This could cause issues if negative line numbers are not expected by the client.
Consider adding an explicit guard for empty files or ensuring the line number is never negative:
🛡️ Suggested fix to prevent negative line numbers
def code_pos_to_coordinates(self, pos: int) -> tuple[int, int]: """Helper method to convert a code position to line and column. Args: pos (int): The 0-indexed position in the code. Returns: tuple[int, int]: The line and column, 0-or-1-indexed. """ lines = self.source_code.split("\n") line = 1 if lines else 0 col = 0 for i, line_code in enumerate(lines): if pos <= len(line_code): line = i + 1 col = pos break pos -= len(line_code) + 1 else: if lines: line = len(lines) col = len(lines[-1]) if self.columns_start_at_one: col += 1 if not self.lines_start_at_one: line -= 1 + line = max(0, line) # Ensure non-negative return (line, col)
🤖 Fix all issues with AI agents
In @python/mqt/debugger/dap/dap_server.py:
- Around line 521-555: The unreachable ternary in _build_parse_error_highlight
should be simplified: remove the redundant "if end_column > 0 else column"
because end_column is computed as max(column, len(line_text) + 1) and column is
already clamped to >=1, so end_column will always be >0; update the returned
range end column to use end_column directly (or compute end_column differently
if you intended a different fallback) and remove the unnecessary conditional
expression to clean up the code.
- Around line 464-489: In _build_highlight_entry, make the snippet slice
consistent with the range calculation: compute end_position_exclusive as already
done and then set snippet =
self.source_code[start_pos:end_position_exclusive].replace("\r", "") (and keep
the .strip() downstream), so the text used for "code" matches the computed
range; update the reference to snippet accordingly and ensure message handling
remains unchanged.
📜 Review details
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
📒 Files selected for processing (1)
python/mqt/debugger/dap/dap_server.py
🔇 Additional comments (12)
python/mqt/debugger/dap/dap_server.py (12)
118-119: LGTM! Clean state initialization.The new instance variables for managing highlights and exit prevention are properly typed and initialized.
172-186: LGTM! Exit prevention flag properly managed.The reset logic ensures that after execution commands (step, continue, restart, launch), the exit prevention flag is cleared, allowing the highlight emission logic to set it if needed.
257-261: LGTM! Highlight emission logic is correct.The logic properly emits pending highlights, clears the queue, and prevents premature exit, ensuring users can see error highlights before the debugger terminates.
271-275: LGTM! Exit condition properly guards against premature termination.The added
_prevent_exitcheck ensures the debugger doesn't exit immediately after emitting error highlights, giving the client time to display them.
355-361: LGTM! Assertion failure handling improved with structured highlights.The change to "stderr" category is appropriate for error messages, and the new highlight collection logic provides structured error information to the client.
431-462: LGTM! Robust highlight collection with good fallback handling.The method properly handles edge cases (missing source_code, RuntimeError from diagnostics) and provides a sensible fallback when no specific error causes are found.
491-498: LGTM! Clean mapping of error types to reason identifiers.The static method provides a clear mapping from internal error types to client-facing identifiers, with an appropriate default for unknown types.
500-505: Verify: Parse error replacement vs accumulation.The method replaces
pending_highlightsentirely (self.pending_highlights = [entry]) rather than appending. This means:
- Only the most recent parse error is retained
- Any previously queued highlights are discarded
If multiple parse errors can occur before emission, consider whether accumulation would be more appropriate:
# Current behavior: replace self.pending_highlights = [entry] # Alternative: accumulate self.pending_highlights.append(entry)If parse errors are mutually exclusive or stop execution immediately, the current approach is correct.
507-519: LGTM! Robust error message parsing with sensible defaults.The regex pattern correctly parses standard compiler error format, and the fallback to line 1, column 1 ensures the method always returns valid coordinates.
557-580: LGTM! Robust recursive flattening with good filtering.The method correctly handles nested structures and filters out empty/null values, ensuring clean message output.
582-608: LGTM! Clean refactor with improved message routing.The addition of the
categoryparameter enables proper message categorization (console/stdout/stderr), and the delegation tosend_message_simpleimproves code organization.
610-649: LGTM! Well-structured simple message sender.The method provides a clean API for sending simple messages with optional title, body, and end segments, properly integrating source location and output category.
| def _build_highlight_entry(self, instruction: int, reason: str, message: str) -> dict[str, Any] | None: | ||
| """Create a highlight entry for a specific instruction.""" | ||
| try: | ||
| start_pos, end_pos = self.simulation_state.get_instruction_position(instruction) | ||
| except RuntimeError: | ||
| return None | ||
| start_line, start_column = self.code_pos_to_coordinates(start_pos) | ||
| if end_pos < len(self.source_code) and self.source_code[end_pos] == "\n": | ||
| end_position_exclusive = end_pos | ||
| else: | ||
| end_position_exclusive = min(len(self.source_code), end_pos + 1) | ||
| end_line, end_column = self.code_pos_to_coordinates(end_position_exclusive) | ||
| snippet = self.source_code[start_pos : end_pos + 1].replace("\r", "") | ||
| message = message.strip() if isinstance(message, str) else "" | ||
| if not message: | ||
| message = "Potential error cause." | ||
| return { | ||
| "instruction": int(instruction), | ||
| "range": { | ||
| "start": {"line": start_line, "column": start_column}, | ||
| "end": {"line": end_line, "column": end_column}, | ||
| }, | ||
| "reason": reason, | ||
| "code": snippet.strip(), | ||
| "message": message, | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick | 🔵 Trivial
Note: Minor inconsistency between snippet and range calculation.
The method is well-structured and handles errors properly. However, there's a subtle difference:
- The
snippeton line 476 always includessource_code[start_pos:end_pos+1] - The
end_position_exclusivecalculation (lines 471-475) varies based on whetherend_posis at a newline
Since the snippet is .strip()-ed, this likely doesn't cause issues in practice, but for perfect consistency, consider using the same end position for both:
♻️ Optional refactor for consistency
def _build_highlight_entry(self, instruction: int, reason: str, message: str) -> dict[str, Any] | None:
"""Create a highlight entry for a specific instruction."""
try:
start_pos, end_pos = self.simulation_state.get_instruction_position(instruction)
except RuntimeError:
return None
start_line, start_column = self.code_pos_to_coordinates(start_pos)
if end_pos < len(self.source_code) and self.source_code[end_pos] == "\n":
end_position_exclusive = end_pos
else:
end_position_exclusive = min(len(self.source_code), end_pos + 1)
end_line, end_column = self.code_pos_to_coordinates(end_position_exclusive)
- snippet = self.source_code[start_pos : end_pos + 1].replace("\r", "")
+ snippet = self.source_code[start_pos : end_position_exclusive].replace("\r", "")
message = message.strip() if isinstance(message, str) else ""
if not message:
message = "Potential error cause."
return {
"instruction": int(instruction),
"range": {
"start": {"line": start_line, "column": start_column},
"end": {"line": end_line, "column": end_column},
},
"reason": reason,
"code": snippet.strip(),
"message": message,
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| def _build_highlight_entry(self, instruction: int, reason: str, message: str) -> dict[str, Any] | None: | |
| """Create a highlight entry for a specific instruction.""" | |
| try: | |
| start_pos, end_pos = self.simulation_state.get_instruction_position(instruction) | |
| except RuntimeError: | |
| return None | |
| start_line, start_column = self.code_pos_to_coordinates(start_pos) | |
| if end_pos < len(self.source_code) and self.source_code[end_pos] == "\n": | |
| end_position_exclusive = end_pos | |
| else: | |
| end_position_exclusive = min(len(self.source_code), end_pos + 1) | |
| end_line, end_column = self.code_pos_to_coordinates(end_position_exclusive) | |
| snippet = self.source_code[start_pos : end_pos + 1].replace("\r", "") | |
| message = message.strip() if isinstance(message, str) else "" | |
| if not message: | |
| message = "Potential error cause." | |
| return { | |
| "instruction": int(instruction), | |
| "range": { | |
| "start": {"line": start_line, "column": start_column}, | |
| "end": {"line": end_line, "column": end_column}, | |
| }, | |
| "reason": reason, | |
| "code": snippet.strip(), | |
| "message": message, | |
| } | |
| def _build_highlight_entry(self, instruction: int, reason: str, message: str) -> dict[str, Any] | None: | |
| """Create a highlight entry for a specific instruction.""" | |
| try: | |
| start_pos, end_pos = self.simulation_state.get_instruction_position(instruction) | |
| except RuntimeError: | |
| return None | |
| start_line, start_column = self.code_pos_to_coordinates(start_pos) | |
| if end_pos < len(self.source_code) and self.source_code[end_pos] == "\n": | |
| end_position_exclusive = end_pos | |
| else: | |
| end_position_exclusive = min(len(self.source_code), end_pos + 1) | |
| end_line, end_column = self.code_pos_to_coordinates(end_position_exclusive) | |
| snippet = self.source_code[start_pos : end_position_exclusive].replace("\r", "") | |
| message = message.strip() if isinstance(message, str) else "" | |
| if not message: | |
| message = "Potential error cause." | |
| return { | |
| "instruction": int(instruction), | |
| "range": { | |
| "start": {"line": start_line, "column": start_column}, | |
| "end": {"line": end_line, "column": end_column}, | |
| }, | |
| "reason": reason, | |
| "code": snippet.strip(), | |
| "message": message, | |
| } |
🤖 Prompt for AI Agents
In @python/mqt/debugger/dap/dap_server.py around lines 464 - 489, In
_build_highlight_entry, make the snippet slice consistent with the range
calculation: compute end_position_exclusive as already done and then set snippet
= self.source_code[start_pos:end_position_exclusive].replace("\r", "") (and keep
the .strip() downstream), so the text used for "code" matches the computed
range; update the reference to snippet accordingly and ensure message handling
remains unchanged.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
🤖 Fix all issues with AI agents
In @python/mqt/debugger/dap/dap_server.py:
- Around line 516-550: _build_parse_error_highlight may produce a zero-width
highlight when column == end_column; change the end_column calculation to ensure
at least one column is highlighted and drop the redundant conditional.
Specifically, in _build_parse_error_highlight compute end_column = max(column +
1, len(line_text) + 1) (so highlights at least one column or to end of line) and
remove the later ternary that checks "if end_column > 0 else column".
In @src/backend/dd/DDSimDebug.cpp:
- Around line 1153-1159: The file contains unresolved Git merge markers
(<<<<<<<, =======, >>>>>>>) that stop compilation; remove the conflict markers
and keep the readiness check: call toDDSimulationState(self), then if
(!ddsim->ready) return ERROR; ensuring that the ready-check block (ddsim->ready)
is retained and merged into the surrounding function where the markers appear so
compilation and the intended state validation are preserved.
📜 Review details
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
📒 Files selected for processing (2)
python/mqt/debugger/dap/dap_server.pysrc/backend/dd/DDSimDebug.cpp
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-11-05T07:42:45.507Z
Learnt from: burgholzer
Repo: munich-quantum-toolkit/core PR: 1287
File: test/qdmi/dd/error_handling_test.cpp:118-194
Timestamp: 2025-11-05T07:42:45.507Z
Learning: In the munich-quantum-toolkit/core QDMI device API, session parameters can only be set before calling `device_session_init()`. Once a session is initialized, any attempt to set a parameter returns `QDMI_ERROR_BADSTATE`. Since `SessionGuard` (in test/qdmi/dd/helpers/test_utils.hpp) automatically initializes the session in its constructor, tests that need to verify session parameter setting behavior before initialization must allocate a separate uninitialized session rather than reusing the `SessionGuard`'s session.
Applied to files:
src/backend/dd/DDSimDebug.cpp
📚 Learning: 2025-11-27T08:52:06.428Z
Learnt from: lsschmid
Repo: munich-quantum-toolkit/qmap PR: 832
File: src/hybridmap/HybridNeutralAtomMapper.cpp:1379-1383
Timestamp: 2025-11-27T08:52:06.428Z
Learning: In src/hybridmap/HybridNeutralAtomMapper.cpp, the getBestMovePos function throws std::runtime_error when no move position is found (finalBestPos.coords is empty). This behavior is acceptable for current use cases since tight architectures are not a concern. A future improvement would be to fall back to SWAP-based strategies instead of throwing.
Applied to files:
src/backend/dd/DDSimDebug.cpp
🧬 Code graph analysis (2)
python/mqt/debugger/dap/dap_server.py (3)
python/mqt/debugger/dap/messages/next_dap_message.py (1)
NextDAPMessage(21-47)python/mqt/debugger/dap/messages/highlight_error_dap_message.py (2)
HighlightError(22-179)encode(62-70)python/mqt/debugger/dap/messages/output_dap_event.py (2)
encode(54-69)OutputDAPEvent(18-69)
src/backend/dd/DDSimDebug.cpp (2)
include/frontend/cli/CliFrontEnd.hpp (2)
code(46-46)code(68-68)include/backend/dd/DDSimDebug.hpp (1)
preprocessAssertionCode(678-678)
🪛 Cppcheck (2.19.0)
src/backend/dd/DDSimDebug.cpp
[error] 1154-1154: syntax error
(syntaxError)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (9)
- GitHub Check: 🐍 Test (windows-2022) / 🐍 windows-2022
- GitHub Check: 🐍 Test (ubuntu-24.04) / 🐍 ubuntu-24.04
- GitHub Check: 🇨 Test 🏁 (windows-2022, msvc, Debug) / 🏁 windows-2022 msvc Debug
- GitHub Check: 🇨 Test 🏁 (windows-11-arm, msvc, Release) / 🏁 windows-11-arm msvc Release
- GitHub Check: 🐍 Test (ubuntu-24.04-arm) / 🐍 ubuntu-24.04-arm
- GitHub Check: 🇨 Test 🐧 (ubuntu-24.04, gcc, Release) / 🐧 ubuntu-24.04 gcc Release
- GitHub Check: 🇨 Test 🏁 (windows-2022, msvc, Release) / 🏁 windows-2022 msvc Release
- GitHub Check: 🇨 Test 🐧 (ubuntu-24.04, gcc, Debug) / 🐧 ubuntu-24.04 gcc Debug
- GitHub Check: 🇨 Lint / 🚨 Lint
🔇 Additional comments (19)
src/backend/dd/DDSimDebug.cpp (6)
78-84: LGTM: Clean implementation of error message retrieval.The function correctly returns
nullptrwhen no error message is present and provides access to the error string otherwise. The lifetime of the returned pointer is safely tied to theDDSimulationStateobject.
524-524: LGTM: Properly exposes the new API.The assignment correctly wires up the
getLastErrorMessagefunction pointer in the public interface.
584-584: LGTM: Proper error state reset.Clearing the error message on initialization ensures a clean starting state.
603-619: LGTM: Comprehensive state reset before code loading.The implementation correctly:
- Sets
ready = falseto prevent operations during loading- Clears all state-tracking containers
- Resets the error message for a clean loading state
626-636: LGTM: Robust exception handling with error message capture.The implementation properly:
- Captures exception messages from
std::exception- Provides a fallback message when the exception message is empty
- Handles unexpected exceptions with a catch-all clause
- Returns ERROR consistently to signal failure
1430-1434: LGTM: Handles edge case for breakpoint positioning.The early return when
desiredPosition < startcorrectly handles the case where a breakpoint is requested before an instruction begins, setting it at the nearest subsequent instruction.python/mqt/debugger/dap/dap_server.py (13)
14-14: LGTM!The
reimport is correctly added to support parse error location extraction.
118-119: LGTM!The new runtime state fields are properly typed and initialized to support the highlight queuing and exit prevention logic.
172-186: LGTM!The logic correctly resets exit prevention when user-initiated commands are executed, allowing normal program flow to resume.
257-261: LGTM!The highlight emission logic correctly sends pending highlights after command processing and prevents premature exit to ensure the user can review the error locations.
271-275: LGTM!The exit condition correctly incorporates the
_prevent_exitflag to ensure the debugger remains active when error highlights need to be displayed.
355-361: LGTM!The assertion failure handler now correctly collects and emits highlight entries for error causes, preventing premature exit to ensure visibility of the highlighted issues.
363-389: LGTM!The refactored coordinate conversion correctly handles edge cases:
- Provides safe defaults for empty source
- Uses
<=to properly include end-of-line positions for highlighting- Adds fallback logic for out-of-bounds positions
These changes improve robustness of position-to-coordinate mapping.
431-457: LGTM!The highlight collection logic is well-structured with appropriate defensive checks:
- Verifies source code availability
- Handles diagnostic retrieval errors gracefully
- Provides sensible fallback highlighting when no error causes are identified
459-484: LGTM!The highlight entry builder correctly handles:
- Position retrieval errors with graceful None return
- Newline boundary cases for accurate highlighting ranges
- Message fallback for robustness
- Proper structure for downstream HighlightError consumption
486-493: LGTM!The highlight reason formatter provides clear, structured identifiers for error cause types with an appropriate fallback.
552-575: LGTM!The message flattening helper correctly handles nested structures (strings, dicts, lists) and safely converts all types to strings, providing robust message formatting.
577-603: LGTM!The refactored
send_message_hierarchycleanly delegates tosend_message_simpleafter normalizing the message structure. The addition of thecategoryparameter improves flexibility for different output types.
605-644: LGTM!The new
send_message_simplemethod provides a clean interface for sending structured messages via OutputDAPEvent. The implementation correctly assembles message segments and handles empty cases gracefully.
| def _build_parse_error_highlight(self, line: int, column: int, detail: str) -> dict[str, Any] | None: | ||
| """Create a highlight entry for a parse error.""" | ||
| if not getattr(self, "source_code", ""): | ||
| return None | ||
| lines = self.source_code.split("\n") | ||
| if not lines: | ||
| return None | ||
| line = max(1, min(line, len(lines))) | ||
| column = max(1, column) | ||
| line_index = line - 1 | ||
| line_text = lines[line_index] | ||
|
|
||
| if column <= 1 and line_index > 0 and not line_text.strip(): | ||
| prev_index = line_index - 1 | ||
| while prev_index >= 0 and not lines[prev_index].strip(): | ||
| prev_index -= 1 | ||
| if prev_index >= 0: | ||
| line_index = prev_index | ||
| line = line_index + 1 | ||
| line_text = lines[line_index] | ||
| stripped = line_text.lstrip() | ||
| column = max(1, len(line_text) - len(stripped) + 1) if stripped else 1 | ||
|
|
||
| end_column = max(column, len(line_text) + 1) | ||
| snippet = line_text.strip() or line_text | ||
| return { | ||
| "instruction": -1, | ||
| "range": { | ||
| "start": {"line": line, "column": column}, | ||
| "end": {"line": line, "column": end_column if end_column > 0 else column}, | ||
| }, | ||
| "reason": "parseError", | ||
| "code": snippet, | ||
| "message": detail, | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick | 🔵 Trivial
Consider highlighting at least one column or to end of line.
The parse error highlight builder is well-structured and handles edge cases defensively. However, at Line 539, when column is large or equals the line length, end_column = max(column, len(line_text) + 1) may create a zero-width highlight (when column == end_column). While technically valid per HighlightError validation, this might not provide clear visual feedback.
Consider ensuring a minimum highlight width or always highlighting to the end of the line for parse errors:
♻️ Suggested refinement
- end_column = max(column, len(line_text) + 1)
+ # Highlight from error column to end of line, ensuring at least 1 character width
+ end_column = max(column + 1, len(line_text) + 1)Additionally, the conditional at Line 545 is redundant since end_column is guaranteed to be >= 1:
- "end": {"line": line, "column": end_column if end_column > 0 else column},
+ "end": {"line": line, "column": end_column},🤖 Prompt for AI Agents
In @python/mqt/debugger/dap/dap_server.py around lines 516 - 550,
_build_parse_error_highlight may produce a zero-width highlight when column ==
end_column; change the end_column calculation to ensure at least one column is
highlighted and drop the redundant conditional. Specifically, in
_build_parse_error_highlight compute end_column = max(column + 1, len(line_text)
+ 1) (so highlights at least one column or to end of line) and remove the later
ternary that checks "if end_column > 0 else column".
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
🤖 Fix all issues with AI agents
In @python/mqt/debugger/dap/dap_server.py:
- Around line 257-261: Duplicate pattern of sending pending highlights, clearing
self.pending_highlights and setting self._prevent_exit = True should be
extracted into a helper; create a method (e.g., _emit_pending_highlights(self,
connection)) that checks self.pending_highlights, builds the
mqt.debugger.dap.messages.HighlightError with self.source_file, calls
send_message(json.dumps(...).encode()) or reuses existing send_message call,
clears self.pending_highlights and sets self._prevent_exit = True, then replace
the code block at the current location and the similar block at the other site
(line with duplicated logic) with calls to this new helper.
- Around line 499-511: The _parse_error_location function currently falls back
to (1, 1, error_message) when the regex doesn't match; update it to emit a
warning containing the unmatched error_message to aid debugging: import and use
the standard logging module (logger = logging.getLogger(__name__)) and call
logger.warning with a clear message and the original error_message in the else
branch of _parse_error_location so you still return the same fallback tuple but
also record that the expected "<input>:line:column: detail" format was not
matched.
📜 Review details
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
📒 Files selected for processing (1)
python/mqt/debugger/dap/dap_server.py
🧰 Additional context used
🧬 Code graph analysis (1)
python/mqt/debugger/dap/dap_server.py (3)
python/mqt/debugger/dap/messages/highlight_error_dap_message.py (2)
HighlightError(22-179)encode(62-70)python/mqt/debugger/dap/messages/output_dap_event.py (1)
encode(54-69)python/mqt/debugger/pydebugger.pyi (3)
get_diagnostics(419-424)potential_error_causes(527-535)get_instruction_position(274-284)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (12)
- GitHub Check: 🇨 Test 🍎 (macos-15-intel, clang, Release) / 🍎 macos-15-intel clang Release
- GitHub Check: 🐍 Test (macos-14) / 🐍 macos-14
- GitHub Check: 🇨 Test 🐧 (ubuntu-24.04-arm, gcc, Release) / 🐧 ubuntu-24.04-arm gcc Release
- GitHub Check: 🇨 Test 🏁 (windows-11-arm, msvc, Release) / 🏁 windows-11-arm msvc Release
- GitHub Check: 🐍 Test (ubuntu-24.04-arm) / 🐍 ubuntu-24.04-arm
- GitHub Check: 🐍 Test (ubuntu-24.04) / 🐍 ubuntu-24.04
- GitHub Check: 🐍 Test (windows-2022) / 🐍 windows-2022
- GitHub Check: 🐍 Test (macos-15-intel) / 🐍 macos-15-intel
- GitHub Check: 🇨 Test 🐧 (ubuntu-24.04, gcc, Release) / 🐧 ubuntu-24.04 gcc Release
- GitHub Check: 🇨 Test 🏁 (windows-2022, msvc, Release) / 🏁 windows-2022 msvc Release
- GitHub Check: 🇨 Test 🏁 (windows-2022, msvc, Debug) / 🏁 windows-2022 msvc Debug
- GitHub Check: 🇨 Lint / 🚨 Lint
🔇 Additional comments (14)
python/mqt/debugger/dap/dap_server.py (14)
14-14: LGTM: Necessary imports added.The
remodule is used for parse error location extraction, andAnyis required for the new type annotations.Also applies to: 17-17
118-119: LGTM: New state fields for highlight management.The
pending_highlightsand_prevent_exitfields support the new highlight emission workflow.
172-186: LGTM: Proper reset of exit prevention flag.The flag is correctly cleared after execution commands, ensuring the debugger can eventually exit after the user continues execution.
271-275: LGTM: Exit prevention integrated into regular checks.The exit condition now respects the
_prevent_exitflag, ensuring the debugger remains active while highlights are being processed by the client.
347-361: LGTM: Assertion failure handling enhanced with highlighting.The category is appropriately set to
"stderr"for assertion failures, and the new highlight collection provides better error context to the client.
431-457: LGTM: Robust highlight collection with fallback.The method includes proper error handling and provides a sensible fallback when no specific error causes are identified, ensuring the user always gets feedback about the assertion failure location.
459-481: LGTM: Correct highlight entry construction.The method properly handles instruction positions and edge cases, including newline characters at the end of instructions. The snippet extraction and coordinate calculation are consistent.
483-490: LGTM: Clear mapping of error cause types.The static method provides clean string identifiers for error causes that can be consumed by the DAP client.
492-497: Note: Parse error replaces pending highlights.Line 497 replaces
pending_highlightswith a single entry rather than appending. This means if multiple parse errors occur in quick succession, only the last one will be queued. This is likely intentional since parse errors typically occur during initial compilation, but verify this aligns with expected behavior.💡 Alternative: Append instead of replace
If multiple parse errors should be shown simultaneously:
def queue_parse_error(self, error_message: str) -> None: """Store highlight data for a parse error to be emitted later.""" line, column, detail = self._parse_error_location(error_message) entry = self._build_parse_error_highlight(line, column, detail) if entry is not None: - self.pending_highlights = [entry] + self.pending_highlights.append(entry)
513-547: LGTM: Sophisticated parse error highlighting with heuristics.The method includes a helpful heuristic (lines 525-534) to find the previous non-empty line when an error is reported on an empty line at column 1. This improves the user experience by highlighting meaningful code rather than blank lines.
Minor note: The ternary check
if end_column > 0 else columnat line 542 is redundant sinceend_columnis guaranteed to be positive (it'smax(column, ...)wherecolumn >= 1), but it doesn't hurt to have defensive code.
549-572: LGTM: Correct recursive message flattening.The method properly handles nested message structures and converts them to a flat list of strings for output. The recursive approach is clean and handles all expected types.
602-641: LGTM: Clean simple message sending implementation.The new method provides a straightforward API for sending simple messages without hierarchical structure. The implementation correctly handles optional title, body, and end components.
574-600: LGTM: Refactored message hierarchy sending.The method now accepts a
categoryparameter and delegates tosend_message_simpleafter flattening the message structure. This is a good refactoring that centralizes the message sending logic. The only caller (at line 347) has been properly updated to pass the category parameter as"stderr", and thesend_message_simplemethod correctly accepts and forwards this parameter.
363-389: The edge case concern is invalid; negative line numbers are not possible.When
source_codeis empty,"".split("\n")returns[''](a single-element list containing an empty string), not an empty list. This means:
line = 1 if lines else 0evaluates to1- The loop executes at least once and the condition
pos <= len(line_code)is satisfied forpos=0and empty stringline = i + 1assigns1before any final adjustment- With the default
lines_start_at_one=True, line stays1; withlines_start_at_one=False, it becomes0The method guarantees
line ≥ 0in all cases. The proposedline = max(0, line)guard is unnecessary.
| if self.pending_highlights: | ||
| highlight_event = mqt.debugger.dap.messages.HighlightError(self.pending_highlights, self.source_file) | ||
| send_message(json.dumps(highlight_event.encode()), connection) | ||
| self.pending_highlights = [] | ||
| self._prevent_exit = True |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick | 🔵 Trivial
LGTM: Pending highlights are emitted correctly.
The highlights are sent, cleared, and the exit prevention flag is set to ensure the user sees the highlights before the debugger exits.
Note: The pattern self._prevent_exit = True after sending highlight events is duplicated here and at line 361. Consider extracting this into a helper method if more emission points are added in the future.
🤖 Prompt for AI Agents
In @python/mqt/debugger/dap/dap_server.py around lines 257 - 261, Duplicate
pattern of sending pending highlights, clearing self.pending_highlights and
setting self._prevent_exit = True should be extracted into a helper; create a
method (e.g., _emit_pending_highlights(self, connection)) that checks
self.pending_highlights, builds the mqt.debugger.dap.messages.HighlightError
with self.source_file, calls send_message(json.dumps(...).encode()) or reuses
existing send_message call, clears self.pending_highlights and sets
self._prevent_exit = True, then replace the code block at the current location
and the similar block at the other site (line with duplicated logic) with calls
to this new helper.
| @staticmethod | ||
| def _parse_error_location(error_message: str) -> tuple[int, int, str]: | ||
| """Parse a compiler error string and extract the source location.""" | ||
| match = re.match(r"<input>:(\d+):(\d+):\s*(.*)", error_message.strip()) | ||
| if match: | ||
| line = int(match.group(1)) | ||
| column = int(match.group(2)) | ||
| detail = match.group(3).strip() | ||
| else: | ||
| line = 1 | ||
| column = 1 | ||
| detail = error_message.strip() | ||
| return (line, column, detail) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick | 🔵 Trivial
LGTM: Parse error location extraction with fallback.
The regex pattern assumes the format <input>:line:column: detail. The fallback to (1, 1, error_message) ensures robustness if the format changes, though consider logging a warning if the pattern doesn't match to aid debugging.
🤖 Prompt for AI Agents
In @python/mqt/debugger/dap/dap_server.py around lines 499 - 511, The
_parse_error_location function currently falls back to (1, 1, error_message)
when the regex doesn't match; update it to emit a warning containing the
unmatched error_message to aid debugging: import and use the standard logging
module (logger = logging.getLogger(__name__)) and call logger.warning with a
clear message and the original error_message in the else branch of
_parse_error_location so you still return the same fallback tuple but also
record that the expected "<input>:line:column: detail" format was not matched.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 3
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
python/mqt/debugger/dap/dap_server.py (1)
579-606:send_state()sends invalid 1-based DAP output events withline=0, column=0andsource.The DAP server is configured with
lines_start_at_one = Trueandcolumns_start_at_one = True, butsend_state()passesline=0, column=0along withself.source_filetosend_message_simple()(line 664), which creates an OutputDAPEvent with these zero-based indices and a source location. This violates DAP protocol expectations—clients interpreting the output event may treat this as an invalid source reference.Omit
sourcewhen sending generic state messages without meaningful code location, or use valid 1-based sentinel values (e.g.,line=1, column=0).
🤖 Fix all issues with AI agents
In @python/mqt/debugger/dap/dap_server.py:
- Around line 118-119: pending_highlights is currently emitted after every
command (including TerminateDAPMessage/DisconnectDAPMessage) and _prevent_exit
is toggled in multiple places; change the logic so highlight emission is skipped
when handling terminate/disconnect/close messages and consolidate _prevent_exit
transitions to only the explicit terminate/close handling paths. Specifically,
before sending or flushing self.pending_highlights check the incoming message
type (e.g. TerminateDAPMessage, DisconnectDAPMessage, close/exit handlers) and
return/skip emission if one of those terminal messages is being processed;
remove or relocate other scattered assignments to self._prevent_exit so it is
only set/cleared in the dedicated terminate/close handler(s) to make session
lifecycle deterministic, and ensure pending_highlights is cleared only after a
successful non-terminal flush.
- Around line 497-553: Summary: _build_parse_error_highlight currently assumes
1-based inputs and can emit out-of-bounds column ranges and ignore the server's
lines_start_at_one / columns_start_at_one settings. Fix: in
_build_parse_error_highlight (and ensure compatibility with values from
_parse_error_location/queue_parse_error) clamp the computed column to the valid
range [1, len(line_text)+1] (or [0, len(line_text)] when columns_start_at_one is
False) and similarly clamp end_column to the same max; then convert the
start/end line and column values to the DAP indexing the server expects using
self.lines_start_at_one and self.columns_start_at_one before placing them into
the returned "range" object so emitted highlights never go out of bounds and
respect the server's 0/1-based settings.
In @src/backend/dd/DDSimDebug.cpp:
- Around line 626-636: Replace the generic fallback messages with
context-specific ones for the ddsimLoadCode operation: in the catch(const
std::exception& e) block, if ddsim->lastErrorMessage is empty set it to a
descriptive string like "ddsimLoadCode: failed to load code" (keeping the actual
e.what() when present), and in the catch(...) block set ddsim->lastErrorMessage
to a similar context-aware message such as "ddsimLoadCode: unknown error while
loading code" so that ddsim->lastErrorMessage clearly indicates the failure
occurred during ddsimLoadCode.
📜 Review details
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
📒 Files selected for processing (2)
python/mqt/debugger/dap/dap_server.pysrc/backend/dd/DDSimDebug.cpp
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-11-05T07:42:45.507Z
Learnt from: burgholzer
Repo: munich-quantum-toolkit/core PR: 1287
File: test/qdmi/dd/error_handling_test.cpp:118-194
Timestamp: 2025-11-05T07:42:45.507Z
Learning: In the munich-quantum-toolkit/core QDMI device API, session parameters can only be set before calling `device_session_init()`. Once a session is initialized, any attempt to set a parameter returns `QDMI_ERROR_BADSTATE`. Since `SessionGuard` (in test/qdmi/dd/helpers/test_utils.hpp) automatically initializes the session in its constructor, tests that need to verify session parameter setting behavior before initialization must allocate a separate uninitialized session rather than reusing the `SessionGuard`'s session.
Applied to files:
src/backend/dd/DDSimDebug.cpp
📚 Learning: 2025-11-27T08:52:06.428Z
Learnt from: lsschmid
Repo: munich-quantum-toolkit/qmap PR: 832
File: src/hybridmap/HybridNeutralAtomMapper.cpp:1379-1383
Timestamp: 2025-11-27T08:52:06.428Z
Learning: In src/hybridmap/HybridNeutralAtomMapper.cpp, the getBestMovePos function throws std::runtime_error when no move position is found (finalBestPos.coords is empty). This behavior is acceptable for current use cases since tight architectures are not a concern. A future improvement would be to fall back to SWAP-based strategies instead of throwing.
Applied to files:
src/backend/dd/DDSimDebug.cpp
🧬 Code graph analysis (1)
src/backend/dd/DDSimDebug.cpp (1)
include/backend/dd/DDSimDebug.hpp (1)
preprocessAssertionCode(678-678)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (15)
- GitHub Check: 🐍 Test (macos-15-intel) / 🐍 macos-15-intel
- GitHub Check: 🐍 Test (windows-2022) / 🐍 windows-2022
- GitHub Check: 🐍 Test (macos-14) / 🐍 macos-14
- GitHub Check: 🇨 Test 🏁 (windows-2022, msvc, Release) / 🏁 windows-2022 msvc Release
- GitHub Check: 🇨 Test 🐧 (ubuntu-24.04, gcc, Debug) / 🐧 ubuntu-24.04 gcc Debug
- GitHub Check: 🇨 Test 🏁 (windows-2022, msvc, Debug) / 🏁 windows-2022 msvc Debug
- GitHub Check: 🇨 Test 🏁 (windows-11-arm, msvc, Release) / 🏁 windows-11-arm msvc Release
- GitHub Check: 🇨 Test 🐧 (ubuntu-24.04, gcc, Release) / 🐧 ubuntu-24.04 gcc Release
- GitHub Check: 🐍 Test (ubuntu-24.04) / 🐍 ubuntu-24.04
- GitHub Check: 🐍 Test (ubuntu-24.04-arm) / 🐍 ubuntu-24.04-arm
- GitHub Check: 🇨 Test 🐧 (ubuntu-24.04-arm, gcc, Release) / 🐧 ubuntu-24.04-arm gcc Release
- GitHub Check: 🇨 Test 🍎 (macos-14, clang, Release) / 🍎 macos-14 clang Release
- GitHub Check: 🇨 Test 🍎 (macos-15-intel, clang, Release) / 🍎 macos-15-intel clang Release
- GitHub Check: 🇨 Test 🍎 (macos-14, clang, Debug) / 🍎 macos-14 clang Debug
- GitHub Check: 🇨 Lint / 🚨 Lint
🔇 Additional comments (5)
src/backend/dd/DDSimDebug.cpp (3)
78-84: LGTM! Clean implementation of error message accessor.The function correctly returns
nullptrfor empty messages and the C-string otherwise. Note that the returned pointer's lifetime is tied toddsim->lastErrorMessageand becomes invalid when the state is modified—this is standard for C APIs where callers are expected to use the pointer immediately.
603-619: Excellent state initialization.The comprehensive clearing of all state-tracking containers (
instructionTypes,instructionStarts,instructionEnds,functionDefinitions,assertionInstructions,successorInstructions,classicalRegisters,qubitRegisters,dataDependencies,functionCallers,targetQubits,instructionObjects) along withlastErrorMessageensures a clean state between code loads. Settingready = falseat the start is correct since the state isn't ready until code loading completes successfully.
1423-1427: The early-return breakpoint behavior is intentional and correctly implements position snapping.When
desiredPositionis before the start of an instruction, the breakpoint is set at that instruction and the function returns OK. This is intentional snapping behavior: if the user attempts to set a breakpoint in whitespace or comments before an instruction, it snaps to the next actual instruction. The test caseBreakpointOutside(setting a breakpoint at position 9999) confirms the overall design is correct—positions beyond all instructions properly return ERROR.python/mqt/debugger/dap/dap_server.py (2)
373-385:code_pos_to_coordinates()boundary handling looks improved.
Thepos <= len(line_code)and final “clamp to last line” fallback should reduce out-of-range coordinates for end-of-line / EOF positions.
355-362: Off-by-one error in snippet extraction within_build_highlight_entry().
get_instruction_position()returns positions with an exclusive end (despite documentation claiming "inclusive"), treating them as Python slice indices. Line 346 correctly usessource_code[start:end], but line 476 incorrectly adds 1:source_code[start_pos : end_pos + 1], including the character at the exclusive boundary. This causes the extracted snippet to over-select when end_pos lands on a terminal character (e.g.,;or\n).The conditional logic at lines 471–475 compounds the issue by adding another
+ 1offset only in the else branch for range calculation, creating inconsistency between snippet content and range coordinates.Proposed fix
def _build_highlight_entry(self, instruction: int, reason: str, message: str) -> dict[str, Any] | None: """Create a highlight entry for a specific instruction.""" try: start_pos, end_pos = self.simulation_state.get_instruction_position(instruction) except RuntimeError: return None start_line, start_column = self.code_pos_to_coordinates(start_pos) - if end_pos < len(self.source_code) and self.source_code[end_pos] == "\n": - end_position_exclusive = end_pos - else: - end_position_exclusive = min(len(self.source_code), end_pos + 1) - end_line, end_column = self.code_pos_to_coordinates(end_position_exclusive) - snippet = self.source_code[start_pos : end_pos + 1].replace("\r", "") + end_line, end_column = self.code_pos_to_coordinates(end_pos) + snippet = self.source_code[start_pos : end_pos].replace("\r", "")Likely an incorrect or invalid review comment.
| self.pending_highlights: list[dict[str, Any]] = [] | ||
| self._prevent_exit = False |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Guard highlight emission + _prevent_exit transitions to avoid “extra” events after terminate/close.
Right now pending_highlights is emitted after every command (including TerminateDAPMessage), which can result in a HighlightError event being sent after termination/exited events. Also _prevent_exit is toggled in multiple places; tightening the conditions makes session behavior more deterministic.
Proposed fix (skip highlight emission after terminate/disconnect; keep `_prevent_exit` behavior intentional)
@@
- if self.pending_highlights:
+ if self.pending_highlights and not isinstance(
+ cmd,
+ (
+ mqt.debugger.dap.messages.TerminateDAPMessage,
+ mqt.debugger.dap.messages.DisconnectDAPMessage,
+ ),
+ ):
highlight_event = mqt.debugger.dap.messages.HighlightError(self.pending_highlights, self.source_file)
send_message(json.dumps(highlight_event.encode()), connection)
self.pending_highlights = []
self._prevent_exit = True
@@
- if (
+ if (
self.simulation_state.is_finished()
and self.simulation_state.get_instruction_count() != 0
and not self._prevent_exit
):Also applies to: 172-187, 257-261, 271-275
🤖 Prompt for AI Agents
In @python/mqt/debugger/dap/dap_server.py around lines 118 - 119,
pending_highlights is currently emitted after every command (including
TerminateDAPMessage/DisconnectDAPMessage) and _prevent_exit is toggled in
multiple places; change the logic so highlight emission is skipped when handling
terminate/disconnect/close messages and consolidate _prevent_exit transitions to
only the explicit terminate/close handling paths. Specifically, before sending
or flushing self.pending_highlights check the incoming message type (e.g.
TerminateDAPMessage, DisconnectDAPMessage, close/exit handlers) and return/skip
emission if one of those terminal messages is being processed; remove or
relocate other scattered assignments to self._prevent_exit so it is only
set/cleared in the dedicated terminate/close handler(s) to make session
lifecycle deterministic, and ensure pending_highlights is cleared only after a
successful non-terminal flush.
| def queue_parse_error(self, error_message: str) -> None: | ||
| """Store highlight data for a parse error to be emitted later.""" | ||
| line, column, detail = self._parse_error_location(error_message) | ||
| entry = self._build_parse_error_highlight(line, column, detail) | ||
| if entry is not None: | ||
| self.pending_highlights = [entry] | ||
|
|
||
| @staticmethod | ||
| def _parse_error_location(error_message: str) -> tuple[int, int, str]: | ||
| """Parse a compiler error string and extract the source location.""" | ||
| match = re.match(r"<input>:(\d+):(\d+):\s*(.*)", error_message.strip()) | ||
| if match: | ||
| line = int(match.group(1)) | ||
| column = int(match.group(2)) | ||
| detail = match.group(3).strip() | ||
| else: | ||
| line = 1 | ||
| column = 1 | ||
| detail = error_message.strip() | ||
| return (line, column, detail) | ||
|
|
||
| def _build_parse_error_highlight(self, line: int, column: int, detail: str) -> dict[str, Any] | None: | ||
| """Create a highlight entry for a parse error.""" | ||
| if not getattr(self, "source_code", ""): | ||
| return None | ||
| lines = self.source_code.split("\n") | ||
| if not lines: | ||
| return None | ||
| line = max(1, min(line, len(lines))) | ||
| column = max(1, column) | ||
| line_index = line - 1 | ||
| line_text = lines[line_index] | ||
|
|
||
| if column <= 1 and line_index > 0 and not line_text.strip(): | ||
| prev_index = line_index - 1 | ||
| while prev_index >= 0 and not lines[prev_index].strip(): | ||
| prev_index -= 1 | ||
| if prev_index >= 0: | ||
| line_index = prev_index | ||
| line = line_index + 1 | ||
| line_text = lines[line_index] | ||
| stripped = line_text.lstrip() | ||
| column = max(1, len(line_text) - len(stripped) + 1) if stripped else 1 | ||
|
|
||
| end_column = max(column, len(line_text) + 1) | ||
| snippet = line_text.strip() or line_text | ||
| return { | ||
| "instruction": -1, | ||
| "range": { | ||
| "start": {"line": line, "column": column}, | ||
| "end": {"line": line, "column": end_column if end_column > 0 else column}, | ||
| }, | ||
| "reason": "parseError", | ||
| "code": snippet, | ||
| "message": detail, | ||
| } | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Parse-error highlight indexing/clamping likely ignores lines_start_at_one / columns_start_at_one and can produce out-of-bounds columns.
_build_parse_error_highlight() assumes 1-based inputs and never clamps column to len(line)+1, but the rest of the server explicitly supports toggling 0/1-based lines/columns.
Proposed fix (clamp column; convert output to current DAP indexing)
@@
def _build_parse_error_highlight(self, line: int, column: int, detail: str) -> dict[str, Any] | None:
@@
- line = max(1, min(line, len(lines)))
- column = max(1, column)
+ # Normalize to 1-based for internal line lookup
+ line = max(1, min(line, len(lines)))
+ column = max(1, column)
@@
- end_column = max(column, len(line_text) + 1)
+ # Clamp to [1, len(line)+1] so we never emit out-of-range columns.
+ column = min(column, len(line_text) + 1)
+ end_column = len(line_text) + 1
snippet = line_text.strip() or line_text
+
+ out_line = line if self.lines_start_at_one else line - 1
+ out_column = column if self.columns_start_at_one else column - 1
+ out_end_column = end_column if self.columns_start_at_one else end_column - 1
return {
@@
"range": {
- "start": {"line": line, "column": column},
- "end": {"line": line, "column": end_column if end_column > 0 else column},
+ "start": {"line": out_line, "column": out_column},
+ "end": {"line": out_line, "column": out_end_column},
},🤖 Prompt for AI Agents
In @python/mqt/debugger/dap/dap_server.py around lines 497 - 553, Summary:
_build_parse_error_highlight currently assumes 1-based inputs and can emit
out-of-bounds column ranges and ignore the server's lines_start_at_one /
columns_start_at_one settings. Fix: in _build_parse_error_highlight (and ensure
compatibility with values from _parse_error_location/queue_parse_error) clamp
the computed column to the valid range [1, len(line_text)+1] (or [0,
len(line_text)] when columns_start_at_one is False) and similarly clamp
end_column to the same max; then convert the start/end line and column values to
the DAP indexing the server expects using self.lines_start_at_one and
self.columns_start_at_one before placing them into the returned "range" object
so emitted highlights never go out of bounds and respect the server's 0/1-based
settings.
| } catch (const std::exception& e) { | ||
| std::cerr << e.what() << "\n"; | ||
| ddsim->lastErrorMessage = e.what(); | ||
| if (ddsim->lastErrorMessage.empty()) { | ||
| ddsim->lastErrorMessage = | ||
| "An error occurred while executing the operation"; | ||
| } | ||
| return ERROR; | ||
| } catch (...) { | ||
| ddsim->lastErrorMessage = "An error occurred while executing the operation"; | ||
| return ERROR; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick | 🔵 Trivial
Consider more context-specific error message.
The exception handling correctly captures error messages and provides a fallback. However, the fallback message "An error occurred while executing the operation" is generic and doesn't clearly indicate the context. Since this is in ddsimLoadCode, a more accurate message would be:
} catch (const std::exception& e) {
ddsim->lastErrorMessage = e.what();
if (ddsim->lastErrorMessage.empty()) {
ddsim->lastErrorMessage =
- "An error occurred while executing the operation";
+ "An error occurred while loading the code";
}
return ERROR;
} catch (...) {
- ddsim->lastErrorMessage = "An error occurred while executing the operation";
+ ddsim->lastErrorMessage = "An error occurred while loading the code";
return ERROR;
}This provides clearer diagnostics when code loading fails.
🤖 Prompt for AI Agents
In @src/backend/dd/DDSimDebug.cpp around lines 626 - 636, Replace the generic
fallback messages with context-specific ones for the ddsimLoadCode operation: in
the catch(const std::exception& e) block, if ddsim->lastErrorMessage is empty
set it to a descriptive string like "ddsimLoadCode: failed to load code"
(keeping the actual e.what() when present), and in the catch(...) block set
ddsim->lastErrorMessage to a similar context-aware message such as
"ddsimLoadCode: unknown error while loading code" so that
ddsim->lastErrorMessage clearly indicates the failure occurred during
ddsimLoadCode.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 4
🤖 Fix all issues with AI agents
In @python/mqt/debugger/dap/messages/launch_dap_message.py:
- Around line 81-83: The variable parsed_successfully is undefined and the
conditional is illogical; either (A) if you intend to always clear highlights on
launch, remove the if and contextlib.suppress and call
server.simulation_state.reset_simulation() unconditionally (also remove the
now-unused contextlib import), or (B) if you only want to reset when parsing
succeeded, introduce and set a parsed_successfully boolean where parsing happens
(e.g., in the load_code/parse block), use that variable in the if and keep the
reset inside the conditional, ensuring parsed_successfully is initialized in all
code paths; update references to parsed_successfully, load_code, and
reset_simulation accordingly.
In @python/mqt/debugger/dap/messages/restart_dap_message.py:
- Around line 73-75: parsed_successfully is undefined and load_code lacks error
handling in restart handler; wrap the load_code call in the same try/except
RuntimeError pattern used in launch_dap_message.py to catch parse errors and
return a proper error response (use the "restart" action in the response), and
fix the conditional around server.simulation_state.reset_simulation by either
removing the undefined parsed_successfully check and always calling
reset_simulation (if intent is to clear highlights regardless) or introducing a
correctly scoped parsed_successfully boolean set from load_code success before
checking it; ensure you mirror the launch handler's control flow and error
messaging to avoid crashing the DAP server.
In @src/backend/dd/DDSimDebug.cpp:
- Around line 627-636: The fallback error string used when catching exceptions
in ddsimLoadCode is too generic; update the assignments to
ddsim->lastErrorMessage in both the std::exception and catch(...) handlers to a
more specific message such as "An error occurred while loading code" (or
"Unknown error while loading code") so the CLI's "Error loading code:" prefix
yields a clearer combined message; locate the two places where
ddsim->lastErrorMessage is set in the ddsimLoadCode error handlers and replace
the generic text accordingly.
- Line 1153: Remove the unused local pointer declaration "auto* ddsim =
toDDSimulationState(self);" — callers already use the "self" pointer and
interface methods directly; delete this line (and any now-empty include or using
if introduced solely for that conversion) to eliminate the unused-variable
warning, leaving the rest of the function to operate on "self" and its methods.
📜 Review details
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
📒 Files selected for processing (4)
python/mqt/debugger/dap/messages/launch_dap_message.pypython/mqt/debugger/dap/messages/restart_dap_message.pysrc/backend/dd/DDSimDebug.cppsrc/frontend/cli/CliFrontEnd.cpp
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-11-05T07:42:45.507Z
Learnt from: burgholzer
Repo: munich-quantum-toolkit/core PR: 1287
File: test/qdmi/dd/error_handling_test.cpp:118-194
Timestamp: 2025-11-05T07:42:45.507Z
Learning: In the munich-quantum-toolkit/core QDMI device API, session parameters can only be set before calling `device_session_init()`. Once a session is initialized, any attempt to set a parameter returns `QDMI_ERROR_BADSTATE`. Since `SessionGuard` (in test/qdmi/dd/helpers/test_utils.hpp) automatically initializes the session in its constructor, tests that need to verify session parameter setting behavior before initialization must allocate a separate uninitialized session rather than reusing the `SessionGuard`'s session.
Applied to files:
src/backend/dd/DDSimDebug.cpp
🧬 Code graph analysis (2)
python/mqt/debugger/dap/messages/restart_dap_message.py (1)
python/mqt/debugger/pydebugger.pyi (1)
reset_simulation(193-198)
python/mqt/debugger/dap/messages/launch_dap_message.py (1)
python/mqt/debugger/pydebugger.pyi (1)
reset_simulation(193-198)
🪛 Ruff (0.14.10)
python/mqt/debugger/dap/messages/restart_dap_message.py
73-73: Undefined name parsed_successfully
(F821)
python/mqt/debugger/dap/messages/launch_dap_message.py
81-81: Undefined name parsed_successfully
(F821)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (15)
- GitHub Check: 🐍 Test (windows-2022) / 🐍 windows-2022
- GitHub Check: 🐍 Test (macos-15-intel) / 🐍 macos-15-intel
- GitHub Check: 🇨 Test 🐧 (ubuntu-24.04-arm, gcc, Release) / 🐧 ubuntu-24.04-arm gcc Release
- GitHub Check: 🇨 Test 🐧 (ubuntu-24.04, gcc, Release) / 🐧 ubuntu-24.04 gcc Release
- GitHub Check: 🇨 Test 🏁 (windows-11-arm, msvc, Release) / 🏁 windows-11-arm msvc Release
- GitHub Check: 🇨 Test 🐧 (ubuntu-24.04, gcc, Debug) / 🐧 ubuntu-24.04 gcc Debug
- GitHub Check: 🇨 Test 🏁 (windows-2022, msvc, Debug) / 🏁 windows-2022 msvc Debug
- GitHub Check: 🐍 Test (ubuntu-24.04-arm) / 🐍 ubuntu-24.04-arm
- GitHub Check: 🇨 Test 🍎 (macos-15-intel, clang, Release) / 🍎 macos-15-intel clang Release
- GitHub Check: 🇨 Test 🏁 (windows-2022, msvc, Release) / 🏁 windows-2022 msvc Release
- GitHub Check: 🇨 Test 🍎 (macos-14, clang, Debug) / 🍎 macos-14 clang Debug
- GitHub Check: 🐍 Test (ubuntu-24.04) / 🐍 ubuntu-24.04
- GitHub Check: 🇨 Test 🍎 (macos-14, clang, Release) / 🍎 macos-14 clang Release
- GitHub Check: 🐍 Test (macos-14) / 🐍 macos-14
- GitHub Check: 🇨 Lint / 🚨 Lint
🔇 Additional comments (6)
src/frontend/cli/CliFrontEnd.cpp (1)
71-82: LGTM! Proper defensive error handling.The implementation correctly handles the C-style function pointer interface with appropriate nullptr checks before calling
getLastErrorMessageand validating the returned message. This enables users to see specific error details when code loading fails.src/backend/dd/DDSimDebug.cpp (5)
78-84: LGTM! Clean error message accessor.The implementation correctly returns
nullptrfor empty error messages rather than a pointer to an empty string, which is good API design and matches the caller's expectations in CliFrontEnd.cpp.
524-524: LGTM! Correct API wiring.The new
getLastErrorMessagefunction pointer is properly assigned during interface initialization.
584-584: LGTM! Proper error state initialization.Clearing
lastErrorMessageinddsimInitensures a clean starting state.
603-619: LGTM! Comprehensive load-time state reset.The code properly clears all internal state containers including the error message at the beginning of
ddsimLoadCode, ensuring each code load starts with a clean slate. Settingready = falseupfront is also correct.
1424-1428: The snap-forward behavior is intentional, well-designed, and adequately tested.All three concerns from the original review are verified:
Monotonic ordering confirmed:
instructionStartsis populated sequentially viapush_back(instruction.originalCodeStartPosition)(line 1691), guaranteeing strict monotonic increase.Behavioral change intentional and tested: The snap-forward logic (lines 1424-1428) is deliberately implemented to handle breakpoints in gaps between instruction ranges. Test coverage validates this indirectly—tests like
TopLevelBreakpointsandInGateDefinitionBreakpointsuse real source positions that may fall before instructions and verify the correct target instruction is selected.Edge cases properly handled:
- Before first instruction: snaps to instruction 0 via early-return (lines 1424-1428)
- Between function definitions: handled explicitly (lines 1430-1449)
- Beyond all instructions: loop completes and returns
ERROR(line 1456), preserving theBreakpointOutsidetest expectationThe snap-forward implementation is logically sound and consistent with typical debugger behavior (positioning breakpoints at the nearest valid instruction when an exact position is unavailable).
| ddsim->lastErrorMessage = e.what(); | ||
| if (ddsim->lastErrorMessage.empty()) { | ||
| ddsim->lastErrorMessage = | ||
| "An error occurred while executing the operation"; | ||
| } | ||
| return ERROR; | ||
| } catch (...) { | ||
| ddsim->lastErrorMessage = "An error occurred while executing the operation"; | ||
| return ERROR; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick | 🔵 Trivial
Consider a more specific fallback error message.
The exception handling is solid, but the fallback message "An error occurred while executing the operation" is quite generic for a code loading context. Since this is in ddsimLoadCode and the CLI will prepend "Error loading code:", consider a more descriptive fallback:
Suggested improvement
ddsim->lastErrorMessage = e.what();
if (ddsim->lastErrorMessage.empty()) {
- ddsim->lastErrorMessage =
- "An error occurred while executing the operation";
+ ddsim->lastErrorMessage = "Failed to parse or import the provided code";
}
return ERROR;
} catch (...) {
- ddsim->lastErrorMessage = "An error occurred while executing the operation";
+ ddsim->lastErrorMessage = "Failed to parse or import the provided code";
return ERROR;
}🤖 Prompt for AI Agents
In @src/backend/dd/DDSimDebug.cpp around lines 627 - 636, The fallback error
string used when catching exceptions in ddsimLoadCode is too generic; update the
assignments to ddsim->lastErrorMessage in both the std::exception and catch(...)
handlers to a more specific message such as "An error occurred while loading
code" (or "Unknown error while loading code") so the CLI's "Error loading code:"
prefix yields a clearer combined message; locate the two places where
ddsim->lastErrorMessage is set in the ddsimLoadCode error handlers and replace
the generic text accordingly.
c845775 to
ccfd5bb
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 5
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
python/mqt/debugger/dap/messages/restart_dap_message.py (1)
81-86: Response command should reflect request type and success should reflect parse result.The response incorrectly returns
"command": "launch"for arestartrequest and hardcodes"success": Truedespite theparsed_successfullyvariable tracking the actual outcome. When code parsing fails (lines 72-75),parsed_successfullyis set toFalseand a parse error is queued, but the response still indicates success. This prevents DAP clients from detecting restart failures.Fix
return { "type": "response", "request_seq": self.sequence_number, - "success": True, - "command": "launch", + "success": parsed_successfully, + "command": "restart", }
🤖 Fix all issues with AI agents
In @python/mqt/debugger/dap/messages/launch_dap_message.py:
- Around line 69-70: The call to locale.getpreferredencoding uses a boolean
positional argument which triggers Ruff FBT003; update the call in
launch_dap_message.py where you read the file (the expression passed to
program_path.read_text) to use the keyword parameter form
locale.getpreferredencoding(do_setlocale=False) so the encoding argument is
explicit—this change affects the line assigning code =
program_path.read_text(encoding=...) before setting server.source_code.
- Around line 67-70: server.source_file currently stores a potentially relative
path via self.program which can cause editor highlights and breakpoints to
mis-map; update the assignment that sets server.source_file (the block assigning
server.source_file = {"name": program_path.name, "path": self.program}) to store
an absolute/normalized path instead, e.g., replace the value for "path" with the
resolved string form of program_path (use program_path.resolve() and convert to
str) so source_file.path is stable across CWD changes.
In @python/mqt/debugger/dap/messages/restart_dap_message.py:
- Line 70: The call to locale.getpreferredencoding uses a positional boolean;
update the call inside the line assigning to code (the
program_path.read_text(...) call) to pass the flag as a keyword: use
locale.getpreferredencoding(do_setlocale=False) so the encoding argument to
program_path.read_text(encoding=...) is supplied via the named parameter.
- Around line 78-80: The current code silently suppresses RuntimeError when
parsed_successfully is False by using contextlib.suppress(RuntimeError);
instead, replace this with targeted handling: either catch a more specific
exception (e.g., SimulationNotRunningError or whichever specific error
reset_simulation() raises) or use try/except RuntimeError as e and log the
exception at debug level before continuing (e.g., logger.debug or
logging.exception) so real bugs aren’t masked; update the block around
parsed_successfully / server.simulation_state.reset_simulation() to perform
narrow exception matching or log the caught RuntimeError with context rather
than suppressing it.
In @src/backend/dd/DDSimDebug.cpp:
- Around line 685-695: Update the documentation comment for the
LoadResult.message field in include/backend/debug.h to state that the returned
char* is owned by the implementation and has the same lifetime constraints as
getLastErrorMessage: it remains valid only until the next interface call that
may modify the error state (i.e., until DDSimulationState::lastErrorMessage is
changed). Reference the LoadResult.message field and mirror the exact lifetime
wording used by getLastErrorMessage so callers understand the pointer is not
heap-owned and must be copied if needed beyond the next interface call.
📜 Review details
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
📒 Files selected for processing (13)
bindings/InterfaceBindings.cppinclude/backend/dd/DDSimDebug.hppinclude/backend/debug.hpython/mqt/debugger/dap/dap_server.pypython/mqt/debugger/dap/messages/__init__.pypython/mqt/debugger/dap/messages/highlight_error_dap_message.pypython/mqt/debugger/dap/messages/launch_dap_message.pypython/mqt/debugger/dap/messages/restart_dap_message.pypython/mqt/debugger/pydebugger.pyisrc/backend/dd/DDSimDebug.cppsrc/common/parsing/CodePreprocessing.cppsrc/frontend/cli/CliFrontEnd.cpptest/python/test_dap_server.py
🧰 Additional context used
🧠 Learnings (3)
📚 Learning: 2025-11-05T07:42:45.507Z
Learnt from: burgholzer
Repo: munich-quantum-toolkit/core PR: 1287
File: test/qdmi/dd/error_handling_test.cpp:118-194
Timestamp: 2025-11-05T07:42:45.507Z
Learning: In the munich-quantum-toolkit/core QDMI device API, session parameters can only be set before calling `device_session_init()`. Once a session is initialized, any attempt to set a parameter returns `QDMI_ERROR_BADSTATE`. Since `SessionGuard` (in test/qdmi/dd/helpers/test_utils.hpp) automatically initializes the session in its constructor, tests that need to verify session parameter setting behavior before initialization must allocate a separate uninitialized session rather than reusing the `SessionGuard`'s session.
Applied to files:
src/backend/dd/DDSimDebug.cpp
📚 Learning: 2025-11-27T08:52:06.428Z
Learnt from: lsschmid
Repo: munich-quantum-toolkit/qmap PR: 832
File: src/hybridmap/HybridNeutralAtomMapper.cpp:1379-1383
Timestamp: 2025-11-27T08:52:06.428Z
Learning: In src/hybridmap/HybridNeutralAtomMapper.cpp, the getBestMovePos function throws std::runtime_error when no move position is found (finalBestPos.coords is empty). This behavior is acceptable for current use cases since tight architectures are not a concern. A future improvement would be to fall back to SWAP-based strategies instead of throwing.
Applied to files:
src/backend/dd/DDSimDebug.cppsrc/common/parsing/CodePreprocessing.cpp
📚 Learning: 2025-11-05T09:23:46.540Z
Learnt from: burgholzer
Repo: munich-quantum-toolkit/core PR: 1287
File: src/qdmi/dd/Device.cpp:492-521
Timestamp: 2025-11-05T09:23:46.540Z
Learning: In the munich-quantum-toolkit/core repository, the `qasm3::Importer::imports()` function is backwards compatible with OpenQASM 2 programs. Therefore, it can be used to parse both QASM2 and QASM3 program formats without requiring separate importers for each version.
Applied to files:
src/backend/dd/DDSimDebug.cpp
🧬 Code graph analysis (7)
include/backend/debug.h (2)
include/frontend/cli/CliFrontEnd.hpp (2)
code(46-46)code(68-68)test/utils/common_fixtures.hpp (6)
code(211-215)code(211-213)code(236-244)code(236-237)code(254-256)code(254-254)
src/backend/dd/DDSimDebug.cpp (1)
include/backend/dd/DDSimDebug.hpp (2)
ddsimLoadCodeWithResult(302-302)ddsimLoadCode(294-294)
src/common/parsing/CodePreprocessing.cpp (3)
include/common/parsing/ParsingError.hpp (1)
ParsingError(32-32)src/common/parsing/ParsingError.cpp (1)
ParsingError(23-23)include/common/parsing/CodePreprocessing.hpp (1)
isVariableDeclaration(296-296)
include/backend/dd/DDSimDebug.hpp (1)
test/utils/common_fixtures.hpp (6)
code(211-215)code(211-213)code(236-244)code(236-237)code(254-256)code(254-254)
test/python/test_dap_server.py (2)
python/mqt/debugger/dap/dap_server.py (2)
code_pos_to_coordinates(362-388)collect_highlight_entries(430-461)python/mqt/debugger/pydebugger.pyi (3)
potential_error_causes(552-560)get_instruction_position(299-309)get_diagnostics(444-449)
src/frontend/cli/CliFrontEnd.cpp (1)
include/frontend/cli/CliFrontEnd.hpp (2)
state(62-63)state(75-75)
python/mqt/debugger/dap/messages/__init__.py (1)
python/mqt/debugger/dap/messages/highlight_error_dap_message.py (1)
HighlightError(22-179)
🪛 Cppcheck (2.19.0)
src/backend/dd/DDSimDebug.cpp
[information] 37-37: Include file
(missingInclude)
src/common/parsing/CodePreprocessing.cpp
[information] 24-24: Include file
(missingIncludeSystem)
[information] 25-25: Include file
(missingIncludeSystem)
[information] 23-23: Include file
(missingIncludeSystem)
[information] 24-24: Include file
(missingIncludeSystem)
[information] 23-23: Include file
(missingIncludeSystem)
[information] 24-24: Include file
(missingIncludeSystem)
[information] 25-25: Include file
(missingIncludeSystem)
[information] 23-23: Include file
(missingIncludeSystem)
[information] 24-24: Include file
(missingIncludeSystem)
[information] 25-25: Include file
(missingIncludeSystem)
🪛 Ruff (0.14.10)
python/mqt/debugger/dap/messages/launch_dap_message.py
69-69: Boolean positional value in function call
(FBT003)
python/mqt/debugger/dap/messages/restart_dap_message.py
70-70: Boolean positional value in function call
(FBT003)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (15)
- GitHub Check: 🇨 Test 🍎 (macos-15-intel, clang, Release) / 🍎 macos-15-intel clang Release
- GitHub Check: 🇨 Test 🍎 (macos-14, clang, Debug) / 🍎 macos-14 clang Debug
- GitHub Check: 🇨 Test 🏁 (windows-2022, msvc, Debug) / 🏁 windows-2022 msvc Debug
- GitHub Check: 🐍 Test (macos-14) / 🐍 macos-14
- GitHub Check: 🇨 Test 🏁 (windows-2022, msvc, Release) / 🏁 windows-2022 msvc Release
- GitHub Check: 🇨 Test 🍎 (macos-14, clang, Release) / 🍎 macos-14 clang Release
- GitHub Check: 🐍 Test (ubuntu-24.04) / 🐍 ubuntu-24.04
- GitHub Check: 🇨 Test 🏁 (windows-11-arm, msvc, Release) / 🏁 windows-11-arm msvc Release
- GitHub Check: 🇨 Test 🐧 (ubuntu-24.04, gcc, Release) / 🐧 ubuntu-24.04 gcc Release
- GitHub Check: 🐍 Test (windows-2022) / 🐍 windows-2022
- GitHub Check: 🇨 Test 🐧 (ubuntu-24.04-arm, gcc, Release) / 🐧 ubuntu-24.04-arm gcc Release
- GitHub Check: 🇨 Test 🐧 (ubuntu-24.04, gcc, Debug) / 🐧 ubuntu-24.04 gcc Debug
- GitHub Check: 🐍 Test (ubuntu-24.04-arm) / 🐍 ubuntu-24.04-arm
- GitHub Check: 🐍 Test (macos-15-intel) / 🐍 macos-15-intel
- GitHub Check: 🇨 Lint / 🚨 Lint
🔇 Additional comments (42)
python/mqt/debugger/dap/messages/launch_dap_message.py (1)
71-79: The proposed None-checks are unnecessary. TheLoadResult.lineandLoadResult.columnfields are typed asint(notOptional[int]), and 0 is a documented sentinel value for "unknown location"—not an invalid state. The code already handles 0 defensively:_build_parse_error_highlightclamps line to[1, num_lines]and column to[1, ∞), so sentinel values are safely converted to valid positions. The current implementation is correct and requires no changes.python/mqt/debugger/dap/messages/restart_dap_message.py (2)
13-13:contextlibimport is fine, but keep exception suppression tightly scoped.
You’re addingcontextlibsolely to suppressRuntimeErrorlater; that suppression should ideally be narrowed/logged (see comment on Line 78-80).
68-77: Parse-result handling: ensure restart clears prior diagnostics andstopOnEntryis truly boolean.
Good: settingserver.source_filebefore load improves traceability, and gatingrun_simulation()on parse success avoids cascading failures. Please ensure any previously queued parse errors / highlight state are cleared on restart somewhere (not shown here), otherwise old diagnostics may persist in the new run. Alsoself.stop_on_entryis declaredbool; verify it can’t be a non-bool truthy value from request parsing, since this now controls whether execution starts.python/mqt/debugger/dap/messages/highlight_error_dap_message.py (5)
47-61: Verify message validation handles None values correctly.The validation on line 58 converts the message to a string before checking if it's empty. If
highlight["message"]isNone,str(None)returns the string"None", which would pass the validation check even though it's not a valid message.Consider adding an explicit check for None:
🛡️ Proposed fix to handle None values
for highlight in self.highlights: - if "message" not in highlight or not str(highlight["message"]).strip(): + message = highlight.get("message") + if message is None or not str(message).strip(): msg = "Each highlight entry must contain a descriptive 'message'." raise ValueError(msg)
22-45: LGTM: Clean initialization with proper normalization.The constructor properly normalizes highlights and source data before calling the parent constructor, ensuring all data is validated and in the correct format.
62-70: LGTM: Standard DAP encoding pattern.The encode method follows the standard DAP event encoding pattern by extending the parent's encoded output with a body containing highlights and source.
111-137: LGTM: Robust position validation.The position normalization includes proper type checking, KeyError handling with informative error messages, and integer conversion.
139-162: LGTM: Defensive source normalization.The method creates a defensive copy and validates required fields (name and path) are present, with clear error messages for violations.
python/mqt/debugger/dap/messages/__init__.py (1)
24-24: LGTM: Proper public API exposure.The new HighlightError class is correctly imported and exported via
__all__, making it available as part of the module's public API. The placement maintains alphabetical order in the export list.Also applies to: 61-61
src/frontend/cli/CliFrontEnd.cpp (1)
72-80: LGTM: Robust error message handling.The enhanced error reporting includes proper defensive checks:
- Validates the
getLastErrorMessagefunction pointer exists before calling it- Checks both that the message is not nullptr and not an empty string
- Falls back to a generic error message when detailed information is unavailable
This follows good practices for optional function pointer usage in C.
test/python/test_dap_server.py (2)
18-25: LGTM: Correctly tests newline position handling.The test verifies that
code_pos_to_coordinatescorrectly handles positions at newline characters, ensuring they stay on the current line with the correct 1-based column index.
28-44: LGTM: Effective mocking for highlight range verification.The test uses
SimpleNamespaceto create a minimal mock of the simulation state, focusing on testing that highlight entries don't incorrectly span multiple instructions. Thetype: ignorecomment on line 38 is appropriate for test mocking scenarios.python/mqt/debugger/pydebugger.pyi (2)
98-111: LGTM: Well-documented LoadResult class.The new
LoadResultclass provides a clean interface for code loading diagnostics with clear documentation:
- Boolean success indicator
- 1-based line and column positions (with 0 indicating unknown)
- Error message string
The documentation explicitly notes the indexing convention, which helps prevent off-by-one errors.
152-160: LGTM: Clear method signature with diagnostic return.The
load_code_with_resultmethod provides an enhanced version ofload_codethat returns structured diagnostic information viaLoadResult. The docstring clearly describes the purpose and return value.include/backend/debug.h (2)
41-61: Well-designed LoadResult struct with proper documentation.The
LoadResultstruct provides a clean API for returning diagnostic information from code loading operations. The 1-based line/column convention with 0 indicating "unavailable" is clearly documented.
79-97: New API entries properly extend the SimulationState interface.The
loadCodeWithResultandgetLastErrorMessagefunction pointers are well-documented. The ownership semantics for the returnedconst char*fromgetLastErrorMessageare clearly specified ("remains valid until the next interface call that modifies the error state").include/backend/dd/DDSimDebug.hpp (2)
122-133: Error state fields properly added to DDSimulationState.The new
lastErrorMessage,lastErrorLine, andlastErrorColumnfields extend the simulation state to track error details. The default initializers (= 0) for line and column ensure a well-defined initial state.
295-302: New API declaration aligns with header contract.The
ddsimLoadCodeWithResultfunction declaration is properly documented and matches theLoadResultreturn type defined indebug.h.bindings/InterfaceBindings.cpp (3)
169-184: LoadResult binding safely handles nullable message field.The
messageproperty uses a lambda that properly handles the case whereself.messageisnullptr, returning an empty string instead. This prevents potential null pointer dereferences in Python code.
194-204: Improved error handling with descriptive messages.The updated
load_codebinding now retrieves error messages viagetLastErrorMessagewhen available, providing more informative exceptions to Python callers. The null check on the function pointer (self->getLastErrorMessage ? ...) is good defensive programming for backward compatibility.
210-225: New load_code_with_result binding with proper availability check.The binding correctly checks if
loadCodeWithResultis available before calling it, throwing a clearruntime_errorif not. This handles the case where older implementations may not support this method.src/common/parsing/CodePreprocessing.cpp (6)
38-49: Clean input validation helper.The
isDigitsfunction properly handles the empty string case and usesstd::ranges::all_ofidiomatically. The cast tounsigned charbefore callingstd::isdigitis correct practice to avoid undefined behavior with negativecharvalues.
51-76: Line/column computation has quadratic complexity for repeated calls.The
lineColumnForOffsetfunction iterates through all characters up tolineStartto count newlines. If called repeatedly for many offsets, this results in O(n²) complexity overall. Consider caching line start positions if performance becomes a concern.For now, this is acceptable given typical file sizes, but worth noting for future optimization.
154-208: Robust target validation with comprehensive error handling.The
validateTargetsfunction properly validates:
- Target format (register name with bracket notation)
- Index is digits-only
- Index is within register bounds
- Respects shadowed registers in function scopes
The early
continuefor empty targets and targets without brackets allows non-indexed references to pass through, which is intentional.
509-513: Improved trueStart calculation accounts for leading whitespace.The updated logic correctly finds the first non-whitespace character within the block and adds
blocksOffsetto account for expanded block content. This ensures more accurate error position reporting.
539-550: Strengthened register declaration parsing with proper error handling.The register size parsing now:
- Validates the size text contains only digits via
isDigits- Uses
std::stoulwith exception handling- Throws
ParsingErrorwith detailed location information on failureThis prevents crashes from malformed register declarations.
615-624: Centralized target validation applied to assertions and general instructions.The
validateTargetscalls ensure consistent validation across both assertion targets and general instruction targets. The context parameter (" in assertion" vs "") provides appropriate error message differentiation.src/backend/dd/DDSimDebug.cpp (6)
79-85: Correct null return for empty error message.The
ddsimGetLastErrorMessagefunction correctly returnsnullptrwhen the error message is empty, matching the documented behavior that callers should check for null.
525-526: Interface function pointers properly wired.Both
loadCodeWithResultandgetLastErrorMessageare correctly assigned to their implementation functions during state initialization.
586-588: Error state properly reset on init.Clearing
lastErrorMessageand resetting line/column to 0 inddsimInitensures a clean error state when the simulation is initialized.
607-625: Comprehensive state clearing in loadCode.All relevant state containers are cleared before loading new code, ensuring no stale data persists between loads. This directly addresses the PR objective of clearing state on restart.
632-670: Comprehensive exception handling with error state capture.The exception handlers properly capture error information from various exception types:
CompilerError: UsesdebugInfofor line/column when availableConstEvalError/TypeCheckError: Captures message, defaults line/column to 1- Generic
std::exception: Captures message with fallback- Catch-all: Sets generic error message
This ensures meaningful error feedback regardless of the failure mode.
1471-1475: Breakpoint handling fix for positions before first instruction.The new early return handles the case where
desiredPositionfalls before the start of any instruction. Instead of failing, it now sets the breakpoint at the first instruction whose start is greater than the desired position. This is a reasonable fallback behavior.python/mqt/debugger/dap/dap_server.py (9)
117-118: State fields for highlight event management.The
pending_highlightslist queues highlight data for emission after command handling, while_prevent_exitensures the client receives highlight events before seeing exit events. This coordination is essential for proper UX.
171-185: Reset _prevent_exit after stepping/execution commands.Resetting
_prevent_exittoFalseafter processing stepping commands ensures the flag only persists for one command cycle. This prevents the exit event from being indefinitely blocked.
256-260: Highlight event emission with proper state management.The pending highlights are emitted as a
HighlightErrorevent, then cleared. Setting_prevent_exit = Trueensures the client has time to process the highlight before receiving any exit event.
270-274: Exit event gated by _prevent_exit flag.Adding
not self._prevent_exitto the condition prevents theExitedDAPEventfrom being sent when highlights are pending. This ensures the client receives and displays the error highlighting before the session terminates.
372-388: Improved edge case handling in code_pos_to_coordinates.The updated logic:
- Initializes
line = 1for empty files (line 372)- Uses
for...elseto handle positions past the end of the file (lines 380-383)- Returns the last line/column when position exceeds file length
This prevents index errors and provides sensible defaults.
430-461: Highlight entry collection with fallback behavior.The
collect_highlight_entriesmethod:
- Collects highlights for each error cause
- Falls back to highlighting the failing instruction if no specific causes are found
- Gracefully handles
RuntimeErrorwhen diagnostics are unavailableThis ensures meaningful feedback even when detailed diagnostics aren't available.
502-536: Parse error highlight with smart line detection.The
_build_parse_error_highlightmethod includes logic to find the previous non-empty line when the error is reported on an empty line at column 1 (lines 514-523). This improves the user experience by highlighting where the actual error likely occurred rather than an empty line.
538-561: Recursive message flattening for complex structures.The
_flatten_message_partsmethod handles nested message structures (strings, dicts with title/body/end, lists) and flattens them into a simple list of strings. The recursion handles arbitrary nesting depths.
591-630: Refactored message sending with clear separation.The
send_message_simplemethod provides a clean interface for sending output events. The separation fromsend_message_hierarchyimproves code organization - the hierarchy method now handles structure normalization while the simple method handles the actual sending.
| server.source_file = {"name": program_path.name, "path": self.program} | ||
| parsed_successfully = True | ||
| code = program_path.read_text(encoding=locale.getpreferredencoding(False)) | ||
| server.source_code = code |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick | 🔵 Trivial
Prefer storing an absolute/normalized source_file.path for stable editor mapping.
If self.program can be relative, highlights/breakpoints can mis-map across CWD changes. Consider str(program_path.resolve()).
Proposed diff
program_path = Path(self.program)
- server.source_file = {"name": program_path.name, "path": self.program}
+ server.source_file = {"name": program_path.name, "path": str(program_path.resolve())}
parsed_successfully = True📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| server.source_file = {"name": program_path.name, "path": self.program} | |
| parsed_successfully = True | |
| code = program_path.read_text(encoding=locale.getpreferredencoding(False)) | |
| server.source_code = code | |
| server.source_file = {"name": program_path.name, "path": str(program_path.resolve())} | |
| parsed_successfully = True | |
| code = program_path.read_text(encoding=locale.getpreferredencoding(False)) | |
| server.source_code = code |
🧰 Tools
🪛 Ruff (0.14.10)
69-69: Boolean positional value in function call
(FBT003)
🤖 Prompt for AI Agents
In @python/mqt/debugger/dap/messages/launch_dap_message.py around lines 67 - 70,
server.source_file currently stores a potentially relative path via self.program
which can cause editor highlights and breakpoints to mis-map; update the
assignment that sets server.source_file (the block assigning server.source_file
= {"name": program_path.name, "path": self.program}) to store an
absolute/normalized path instead, e.g., replace the value for "path" with the
resolved string form of program_path (use program_path.resolve() and convert to
str) so source_file.path is stable across CWD changes.
| code = program_path.read_text(encoding=locale.getpreferredencoding(False)) | ||
| server.source_code = code |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fix Ruff FBT003: avoid boolean positional arg in locale.getpreferredencoding.
Proposed diff
- code = program_path.read_text(encoding=locale.getpreferredencoding(False))
+ code = program_path.read_text(encoding=locale.getpreferredencoding(do_setlocale=False))📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| code = program_path.read_text(encoding=locale.getpreferredencoding(False)) | |
| server.source_code = code | |
| code = program_path.read_text(encoding=locale.getpreferredencoding(do_setlocale=False)) | |
| server.source_code = code |
🧰 Tools
🪛 Ruff (0.14.10)
69-69: Boolean positional value in function call
(FBT003)
🤖 Prompt for AI Agents
In @python/mqt/debugger/dap/messages/launch_dap_message.py around lines 69 - 70,
The call to locale.getpreferredencoding uses a boolean positional argument which
triggers Ruff FBT003; update the call in launch_dap_message.py where you read
the file (the expression passed to program_path.read_text) to use the keyword
parameter form locale.getpreferredencoding(do_setlocale=False) so the encoding
argument is explicit—this change affects the line assigning code =
program_path.read_text(encoding=...) before setting server.source_code.
| program_path = Path(self.program) | ||
| server.source_file = {"name": program_path.name, "path": self.program} | ||
| parsed_successfully = True | ||
| code = program_path.read_text(encoding=locale.getpreferredencoding(False)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ruff FBT003: use keyword argument for getpreferredencoding.
Proposed fix
- code = program_path.read_text(encoding=locale.getpreferredencoding(False))
+ code = program_path.read_text(encoding=locale.getpreferredencoding(do_setlocale=False))📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| code = program_path.read_text(encoding=locale.getpreferredencoding(False)) | |
| code = program_path.read_text(encoding=locale.getpreferredencoding(do_setlocale=False)) |
🧰 Tools
🪛 Ruff (0.14.10)
70-70: Boolean positional value in function call
(FBT003)
🤖 Prompt for AI Agents
In @python/mqt/debugger/dap/messages/restart_dap_message.py at line 70, The call
to locale.getpreferredencoding uses a positional boolean; update the call inside
the line assigning to code (the program_path.read_text(...) call) to pass the
flag as a keyword: use locale.getpreferredencoding(do_setlocale=False) so the
encoding argument to program_path.read_text(encoding=...) is supplied via the
named parameter.
src/backend/dd/DDSimDebug.cpp
Outdated
| LoadResult ddsimLoadCodeWithResult(SimulationState* self, const char* code) { | ||
| const auto result = ddsimLoadCode(self, code); | ||
| const auto* ddsim = toDDSimulationState(self); | ||
| LoadResult loadResult{}; | ||
| loadResult.success = (result == OK); | ||
| loadResult.line = ddsim->lastErrorLine; | ||
| loadResult.column = ddsim->lastErrorColumn; | ||
| loadResult.message = | ||
| ddsim->lastErrorMessage.empty() ? "" : ddsim->lastErrorMessage.c_str(); | ||
| return loadResult; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
rg -n -A5 "load_code_with_result" bindings/InterfaceBindings.cppRepository: munich-quantum-toolkit/debugger
Length of output: 531
🏁 Script executed:
rg -n "struct LoadResult" src/Repository: munich-quantum-toolkit/debugger
Length of output: 57
🏁 Script executed:
rg -n "lastErrorMessage" src/backend/dd/Repository: munich-quantum-toolkit/debugger
Length of output: 1096
🏁 Script executed:
rg -n "LoadResult" src/backend/dd/ --type cpp --type hRepository: munich-quantum-toolkit/debugger
Length of output: 247
🏁 Script executed:
fd "DDSimDebug.h" src/Repository: munich-quantum-toolkit/debugger
Length of output: 57
🏁 Script executed:
cat src/backend/dd/DDSimDebug.hRepository: munich-quantum-toolkit/debugger
Length of output: 134
🏁 Script executed:
fd "\.h$" src/ | head -20Repository: munich-quantum-toolkit/debugger
Length of output: 57
🏁 Script executed:
rg -n "LoadResult" --type h src/Repository: munich-quantum-toolkit/debugger
Length of output: 57
🏁 Script executed:
head -50 src/backend/dd/DDSimDebug.cppRepository: munich-quantum-toolkit/debugger
Length of output: 1369
🏁 Script executed:
find src/ -name "DDSimDebug.hpp"Repository: munich-quantum-toolkit/debugger
Length of output: 57
🏁 Script executed:
rg -n "struct LoadResult|using LoadResult|typedef.*LoadResult" src/ bindings/Repository: munich-quantum-toolkit/debugger
Length of output: 57
🏁 Script executed:
rg -n "LoadResult" bindings/Repository: munich-quantum-toolkit/debugger
Length of output: 665
🏁 Script executed:
head -170 bindings/InterfaceBindings.cpp | tail -30Repository: munich-quantum-toolkit/debugger
Length of output: 1453
🏁 Script executed:
rg -n "LoadResult" . --type h --type hppRepository: munich-quantum-toolkit/debugger
Length of output: 102
🏁 Script executed:
find . -name "*.hpp" -o -name "*.h" | xargs rg -l "LoadResult" 2>/dev/nullRepository: munich-quantum-toolkit/debugger
Length of output: 136
🏁 Script executed:
cat ./include/backend/dd/DDSimDebug.hppRepository: munich-quantum-toolkit/debugger
Length of output: 26773
🏁 Script executed:
cat ./include/backend/debug.hRepository: munich-quantum-toolkit/debugger
Length of output: 17649
🏁 Script executed:
rg -A10 "typedef struct {" include/backend/debug.h | grep -A10 "LoadResult"Repository: munich-quantum-toolkit/debugger
Length of output: 170
Add lifetime documentation to LoadResult.message field.
The LoadResult.message field holds a pointer to the internal buffer of DDSimulationState::lastErrorMessage, with the same lifetime constraint as the related getLastErrorMessage function. While the current implementation is safe because Python bindings immediately copy the string, this constraint is not documented in the struct definition, creating an inconsistency with getLastErrorMessage which explicitly documents: "The returned pointer is owned by the implementation and remains valid until the next interface call that modifies the error state."
Update the documentation comment for LoadResult.message in include/backend/debug.h to include the same lifetime constraint notice.
🤖 Prompt for AI Agents
In @src/backend/dd/DDSimDebug.cpp around lines 685 - 695, Update the
documentation comment for the LoadResult.message field in
include/backend/debug.h to state that the returned char* is owned by the
implementation and has the same lifetime constraints as getLastErrorMessage: it
remains valid only until the next interface call that may modify the error state
(i.e., until DDSimulationState::lastErrorMessage is changed). Reference the
LoadResult.message field and mirror the exact lifetime wording used by
getLastErrorMessage so callers understand the pointer is not heap-owned and must
be copied if needed beyond the next interface call.
This reverts commit cf270eb.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
python/mqt/debugger/dap/messages/restart_dap_message.py (1)
87-87: Fix incorrect command field in response.The response returns
"command": "launch"but this is a restart request handler. It should return"command": "restart"to correctly identify the command being responded to.🐛 Proposed fix
return { "type": "response", "request_seq": self.sequence_number, "success": True, - "command": "launch", + "command": "restart", }
🤖 Fix all issues with AI agents
In @python/mqt/debugger/dap/messages/launch_dap_message.py:
- Around line 68-81: The file-loading/parse/run/reset sequence duplicated in
launch_dap_message.py and restart_dap_message.py should be extracted into a
DAPServer helper (e.g., DAPServer.load_program_file(self, program_path: Path,
stop_on_entry: bool) -> bool) that opens the file with
locale.getpreferredencoding(False), assigns self.source_code, calls
self.simulation_state.load_code(code) and on RuntimeError calls
self.queue_parse_error(str(exc)), runs self.simulation_state.run_simulation()
when parsed and not stop_on_entry, and resets the simulation on failure with
contextlib.suppress(RuntimeError) before returning the parsed_successfully
boolean; replace the duplicated blocks in both
LaunchDAPMessage/RestartDAPMessage handlers with setting server.source_file =
{"name": program_path.name, "path": self.program} and a call to
server.load_program_file(program_path, self.stop_on_entry) (or equivalent
attribute names used in those classes).
In @python/mqt/debugger/dap/messages/restart_dap_message.py:
- Around line 69-82: The code that opens program_path, reads code, sets
server.source_code, calls server.simulation_state.load_code, handles
RuntimeError by setting parsed_successfully and calling
server.queue_parse_error, then conditionally calling
server.simulation_state.run_simulation or reset_simulation is duplicated with
launch_dap_message.py; extract a shared helper (e.g., a function like
load_and_start_simulation(server, program_path, stop_on_entry)) that
encapsulates reading the file, assigning server.source_code, calling
simulation_state.load_code with exception handling (queue_parse_error on
failure) and running or resetting the simulation based on parsed_successfully
and stop_on_entry, then replace the duplicated block in restart_dap_message.py
and launch_dap_message.py with a call to that helper to ensure consistency.
📜 Review details
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
📒 Files selected for processing (8)
bindings/InterfaceBindings.cppinclude/backend/dd/DDSimDebug.hppinclude/backend/debug.hpython/mqt/debugger/dap/dap_server.pypython/mqt/debugger/dap/messages/highlight_error_dap_message.pypython/mqt/debugger/dap/messages/launch_dap_message.pypython/mqt/debugger/dap/messages/restart_dap_message.pysrc/backend/dd/DDSimDebug.cpp
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-11-05T07:42:45.507Z
Learnt from: burgholzer
Repo: munich-quantum-toolkit/core PR: 1287
File: test/qdmi/dd/error_handling_test.cpp:118-194
Timestamp: 2025-11-05T07:42:45.507Z
Learning: In the munich-quantum-toolkit/core QDMI device API, session parameters can only be set before calling `device_session_init()`. Once a session is initialized, any attempt to set a parameter returns `QDMI_ERROR_BADSTATE`. Since `SessionGuard` (in test/qdmi/dd/helpers/test_utils.hpp) automatically initializes the session in its constructor, tests that need to verify session parameter setting behavior before initialization must allocate a separate uninitialized session rather than reusing the `SessionGuard`'s session.
Applied to files:
src/backend/dd/DDSimDebug.cpp
📚 Learning: 2025-11-27T08:52:06.428Z
Learnt from: lsschmid
Repo: munich-quantum-toolkit/qmap PR: 832
File: src/hybridmap/HybridNeutralAtomMapper.cpp:1379-1383
Timestamp: 2025-11-27T08:52:06.428Z
Learning: In src/hybridmap/HybridNeutralAtomMapper.cpp, the getBestMovePos function throws std::runtime_error when no move position is found (finalBestPos.coords is empty). This behavior is acceptable for current use cases since tight architectures are not a concern. A future improvement would be to fall back to SWAP-based strategies instead of throwing.
Applied to files:
src/backend/dd/DDSimDebug.cpp
🧬 Code graph analysis (4)
python/mqt/debugger/dap/messages/launch_dap_message.py (2)
python/mqt/debugger/pydebugger.pyi (3)
load_code(130-135)run_simulation(183-188)reset_simulation(193-198)python/mqt/debugger/dap/dap_server.py (1)
queue_parse_error(497-502)
src/backend/dd/DDSimDebug.cpp (2)
include/frontend/cli/CliFrontEnd.hpp (2)
code(46-46)code(68-68)include/backend/dd/DDSimDebug.hpp (1)
preprocessAssertionCode(678-678)
python/mqt/debugger/dap/messages/restart_dap_message.py (2)
python/mqt/debugger/pydebugger.pyi (3)
load_code(130-135)run_simulation(183-188)reset_simulation(193-198)python/mqt/debugger/dap/dap_server.py (1)
queue_parse_error(497-502)
python/mqt/debugger/dap/messages/highlight_error_dap_message.py (3)
python/mqt/debugger/dap/messages/dap_event.py (1)
DAPEvent(17-41)python/mqt/debugger/dap/messages/launch_dap_message.py (1)
validate(44-55)python/mqt/debugger/dap/messages/restart_dap_message.py (1)
validate(44-55)
🪛 Ruff (0.14.10)
python/mqt/debugger/dap/messages/launch_dap_message.py
69-69: Boolean positional value in function call
(FBT003)
python/mqt/debugger/dap/messages/restart_dap_message.py
70-70: Boolean positional value in function call
(FBT003)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (15)
- GitHub Check: 🐍 Test (macos-14) / 🐍 macos-14
- GitHub Check: 🇨 Test 🐧 (ubuntu-24.04, gcc, Debug) / 🐧 ubuntu-24.04 gcc Debug
- GitHub Check: 🐍 Test (windows-2022) / 🐍 windows-2022
- GitHub Check: 🇨 Test 🐧 (ubuntu-24.04-arm, gcc, Release) / 🐧 ubuntu-24.04-arm gcc Release
- GitHub Check: 🇨 Test 🏁 (windows-2022, msvc, Debug) / 🏁 windows-2022 msvc Debug
- GitHub Check: 🐍 Test (ubuntu-24.04-arm) / 🐍 ubuntu-24.04-arm
- GitHub Check: 🐍 Test (ubuntu-24.04) / 🐍 ubuntu-24.04
- GitHub Check: 🐍 Test (macos-15-intel) / 🐍 macos-15-intel
- GitHub Check: 🇨 Test 🏁 (windows-2022, msvc, Release) / 🏁 windows-2022 msvc Release
- GitHub Check: 🇨 Test 🏁 (windows-11-arm, msvc, Release) / 🏁 windows-11-arm msvc Release
- GitHub Check: 🇨 Test 🍎 (macos-14, clang, Release) / 🍎 macos-14 clang Release
- GitHub Check: 🇨 Test 🐧 (ubuntu-24.04, gcc, Release) / 🐧 ubuntu-24.04 gcc Release
- GitHub Check: 🇨 Test 🍎 (macos-14, clang, Debug) / 🍎 macos-14 clang Debug
- GitHub Check: 🇨 Test 🍎 (macos-15-intel, clang, Release) / 🍎 macos-15-intel clang Release
- GitHub Check: 🇨 Lint / 🚨 Lint
🔇 Additional comments (22)
include/backend/dd/DDSimDebug.hpp (1)
122-125: LGTM! Error message storage added.The
lastErrorMessagefield integrates cleanly with the newgetLastErrorMessageAPI to provide detailed error diagnostics.bindings/InterfaceBindings.cpp (1)
177-187: Excellent error handling improvement.The binding now retrieves detailed error messages via
getLastErrorMessagewhen available, providing much better debugging information to Python callers. The defensive null check ensures backward compatibility.python/mqt/debugger/dap/messages/launch_dap_message.py (1)
69-69: Static analysis false positive - standard library API.The
FBT003warning about the boolean positional argument is a false positive. Thelocale.getpreferredencoding(False)call follows the standard library API whereFalsedisables locale aliasing.include/backend/debug.h (1)
57-66: Well-documented API addition.The
getLastErrorMessagefunction pointer is a clean extension to the public interface. The documentation clearly specifies ownership, lifetime, and return semantics, which is essential for C API consumers.python/mqt/debugger/dap/messages/restart_dap_message.py (1)
70-70: Static analysis false positive - standard library API.The
FBT003warning about the boolean positional argument is a false positive. Thelocale.getpreferredencoding(False)call follows the standard library API whereFalsedisables locale aliasing.src/backend/dd/DDSimDebug.cpp (5)
78-84: Verify lifetime guarantees for returned C-string pointer.The function returns a pointer to the internal buffer of
lastErrorMessageviac_str(). This pointer becomes invalid iflastErrorMessageis modified or cleared. While the header documentation mentions the caller doesn't own the string, ensure all call sites understand the pointer is only valid until the next operation that might modifylastErrorMessage(e.g.,loadCode,init).Based on the header comment in
include/backend/debug.hthat documents ownership.
621-636: Robust error handling implementation.The exception handling properly captures error messages from both
std::exceptionand unknown exceptions, with a sensible fallback for empty messages. This ensures users always receive actionable feedback when code loading fails.
603-619: Comprehensive state reset on code load.The extensive clearing of all instruction-related state containers ensures no stale data persists between code loads. Setting
ready = falseearly prevents operations on partially-loaded state.
1153-1153: LGTM - Early retrieval of typed simulation state.Retrieving the
DDSimulationStatepointer at the function start is a reasonable refactoring that makes the typed state available for the entire function scope.
1424-1428: The early-return logic fordesiredPosition < startis intentional and correct. This condition handles character positions that fall in gaps between instructions, mapping them to the next instruction. The comprehensive test cases (e.g.,TopLevelBreakpoints,InGateDefinitionBreakpoints) verify this behavior: positions like 174 map to instruction 4, position 451 to instruction 12, etc. The design is sound—any valid character position maps to either its containing instruction or the next one. Positions outside all instructions return ERROR (after the loop completes without match).python/mqt/debugger/dap/messages/highlight_error_dap_message.py (4)
47-60: LGTM - Validation ensures meaningful error feedback.The validation correctly ensures at least one highlight exists and each contains a descriptive message. The structural validation of ranges and positions is appropriately handled during normalization in the constructor.
72-109: Excellent defensive normalization with sensible defaults.The method thoroughly validates the highlight structure while providing reasonable defaults for optional fields. The type checks for
Mappingand range validation ensure malformed data is caught early with clear error messages.
111-137: LGTM - Position normalization with clear error messages.The method properly validates position structure and converts keys to integers. The error handling provides actionable feedback when required fields are missing. The absence of non-negative validation is acceptable since range ordering is validated separately.
164-179: LGTM - Elegant range ordering validation.The tuple comparison provides clean lexicographic ordering of positions. Using
get()with defaults is safe here since_normalize_positionensures positions are valid before this check is performed.python/mqt/debugger/dap/dap_server.py (8)
118-119: LGTM - Clear state initialization.The new runtime state fields are properly initialized with appropriate types and default values.
172-186: LGTM - Appropriate reset of exit prevention on user actions.Resetting
_prevent_exitwhen the user explicitly steps, continues, or restarts ensures the debugger doesn't stay open unnecessarily after user interaction.
257-261: LGTM - Proper highlight emission and state management.The sequence correctly emits pending highlights, clears the queue to prevent duplicates, and sets the exit prevention flag to ensure users can view the highlighted errors.
373-389: LGTM - Improved edge case handling for out-of-bounds positions.The modified logic now handles positions beyond the file end by setting the location to the end of the last line. The for-else construct correctly differentiates between finding a position (with break) and reaching the end without finding it.
431-462: LGTM - Robust highlight collection with sensible fallback.The method properly handles missing diagnostics and ensures at least the failing instruction is highlighted when no specific error causes are identified. The defensive attribute check for
source_codeprevents errors when called before initialization.
504-516: LGTM - Robust parse error location extraction.The regex correctly extracts line, column, and detail from standard compiler error format, with appropriate fallback for non-standard messages.
554-646: Excellent refactoring - improved separation of concerns.The new message sending architecture cleanly separates:
- Message structure flattening (
_flatten_message_parts)- Hierarchy-specific processing (
send_message_hierarchy)- Core sending logic (
send_message_simple)This improves maintainability and makes the code easier to test and extend.
518-552: Excellent parse error positioning with smart fallback logic.The method intelligently handles edge cases like empty lines by searching backwards for meaningful content. The clamping of coordinates prevents out-of-bounds errors.
Minor: Redundant check in column calculation
Line 539's ternary
if stripped else 1is redundant since it's already inside anif stripped:block at line 538. The condition will always be true. Consider simplifying:- column = max(1, len(line_text) - len(stripped) + 1) if stripped else 1 + column = max(1, len(line_text) - len(stripped) + 1)Likely an incorrect or invalid review comment.
| parsed_successfully = True | ||
| with program_path.open("r", encoding=locale.getpreferredencoding(False)) as f: | ||
| code = f.read() | ||
| server.source_code = code | ||
| try: | ||
| server.simulation_state.load_code(code) | ||
| except RuntimeError as exc: | ||
| parsed_successfully = False | ||
| server.queue_parse_error(str(exc)) | ||
| if parsed_successfully and not self.stop_on_entry: | ||
| server.simulation_state.run_simulation() | ||
| if not parsed_successfully: | ||
| with contextlib.suppress(RuntimeError): | ||
| server.simulation_state.reset_simulation() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick | 🔵 Trivial
Consider extracting shared code loading logic.
This error handling pattern is duplicated from launch_dap_message.py (lines 68-81). Consider extracting a shared helper method to reduce duplication and ensure consistency across launch and restart flows.
See the suggested refactor in the review comment for launch_dap_message.py lines 68-81.
🧰 Tools
🪛 Ruff (0.14.10)
70-70: Boolean positional value in function call
(FBT003)
🤖 Prompt for AI Agents
In @python/mqt/debugger/dap/messages/restart_dap_message.py around lines 69 -
82, The code that opens program_path, reads code, sets server.source_code, calls
server.simulation_state.load_code, handles RuntimeError by setting
parsed_successfully and calling server.queue_parse_error, then conditionally
calling server.simulation_state.run_simulation or reset_simulation is duplicated
with launch_dap_message.py; extract a shared helper (e.g., a function like
load_and_start_simulation(server, program_path, stop_on_entry)) that
encapsulates reading the file, assigning server.source_code, calling
simulation_state.load_code with exception handling (queue_parse_error on
failure) and running or resetting the simulation based on parsed_successfully
and stop_on_entry, then replace the duplicated block in restart_dap_message.py
and launch_dap_message.py with a call to that helper to ensure consistency.
This reverts commit 6fd5af1.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
python/mqt/debugger/dap/messages/restart_dap_message.py (1)
82-88: DAP response is inconsistent with arestartrequest (and reports success on parse failure).
For DAP, the response"command"should match the request ("restart"), and"success"should reflectparsed_successfully(otherwise clients may treat failures as successful restarts). This is likely a protocol-level bug.Proposed diff
return { "type": "response", "request_seq": self.sequence_number, - "success": True, - "command": "launch", + "success": parsed_successfully, + "command": "restart", }
🤖 Fix all issues with AI agents
In @python/mqt/debugger/dap/messages/launch_dap_message.py:
- Around line 78-80: Replace the silent contextlib.suppress around
server.simulation_state.reset_simulation() so that when parsed_successfully is
False any RuntimeError is caught and logged at debug level (including the
exception info) before being ignored; e.g., wrap the call to
server.simulation_state.reset_simulation() in a try/except RuntimeError as e and
call the module or server logger (e.g., logger.debug or server.logger.debug)
with a short message referencing reset_simulation and the caught exception.
In @python/mqt/debugger/dap/messages/restart_dap_message.py:
- Line 70: The call to locale.getpreferredencoding uses a boolean positional
arg; update the assignment that reads the file with program_path.read_text to
call locale.getpreferredencoding using the keyword form (e.g.,
locale.getpreferredencoding(do_setlocale=False)) so the intent is explicit and
Ruff FBT003 is satisfied; keep the rest of the code unchanged and only replace
the positional boolean in the code = program_path.read_text(encoding=...) line.
📜 Review details
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
📒 Files selected for processing (2)
python/mqt/debugger/dap/messages/launch_dap_message.pypython/mqt/debugger/dap/messages/restart_dap_message.py
🧰 Additional context used
🧬 Code graph analysis (1)
python/mqt/debugger/dap/messages/launch_dap_message.py (2)
python/mqt/debugger/pydebugger.pyi (3)
load_code(130-135)run_simulation(183-188)reset_simulation(193-198)python/mqt/debugger/dap/dap_server.py (1)
queue_parse_error(497-502)
🪛 Ruff (0.14.10)
python/mqt/debugger/dap/messages/restart_dap_message.py
70-70: Boolean positional value in function call
(FBT003)
python/mqt/debugger/dap/messages/launch_dap_message.py
69-69: Boolean positional value in function call
(FBT003)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (15)
- GitHub Check: 🐍 Test (windows-2022) / 🐍 windows-2022
- GitHub Check: 🇨 Test 🐧 (ubuntu-24.04, gcc, Debug) / 🐧 ubuntu-24.04 gcc Debug
- GitHub Check: 🇨 Test 🐧 (ubuntu-24.04-arm, gcc, Release) / 🐧 ubuntu-24.04-arm gcc Release
- GitHub Check: 🐍 Test (ubuntu-24.04-arm) / 🐍 ubuntu-24.04-arm
- GitHub Check: 🇨 Test 🏁 (windows-2022, msvc, Debug) / 🏁 windows-2022 msvc Debug
- GitHub Check: 🐍 Test (ubuntu-24.04) / 🐍 ubuntu-24.04
- GitHub Check: 🇨 Test 🏁 (windows-2022, msvc, Release) / 🏁 windows-2022 msvc Release
- GitHub Check: 🇨 Test 🍎 (macos-14, clang, Debug) / 🍎 macos-14 clang Debug
- GitHub Check: 🐍 Test (macos-14) / 🐍 macos-14
- GitHub Check: 🇨 Test 🏁 (windows-11-arm, msvc, Release) / 🏁 windows-11-arm msvc Release
- GitHub Check: 🐍 Test (macos-15-intel) / 🐍 macos-15-intel
- GitHub Check: 🇨 Test 🐧 (ubuntu-24.04, gcc, Release) / 🐧 ubuntu-24.04 gcc Release
- GitHub Check: 🇨 Test 🍎 (macos-15-intel, clang, Release) / 🍎 macos-15-intel clang Release
- GitHub Check: 🇨 Test 🍎 (macos-14, clang, Release) / 🍎 macos-14 clang Release
- GitHub Check: 🇨 Lint / 🚨 Lint
🔇 Additional comments (3)
python/mqt/debugger/dap/messages/restart_dap_message.py (1)
68-81: Verify parse-failure cleanup doesn’t wipe the queued parse error/highlights.
OnRuntimeError, youqueue_parse_error(...)and then (conditionally)reset_simulation()again. Ifreset_simulation()clears any queued DAP events / highlight state, the user may never see the parse error.If the second reset is only meant to clear stale state, consider either (a) not resetting again on parse failure, or (b) moving reset earlier / making
reset_simulation()not affect queued events.python/mqt/debugger/dap/messages/launch_dap_message.py (2)
67-68: LGTM! Good initialization order.Setting
source_filebefore attempting to parse ensures it's available for error handling and highlight generation. Theparsed_successfullyflag cleanly tracks parse state for the conditional logic below.
81-86: This deviates from the DAP specification—launchresponses should havesuccess: falseon parse failures.The handler intentionally returns
success: Trueeven when parsing fails, deferring error reporting to the customhighlightErrorevent. While this design is consistent (also used inrestartmessages) and allows the debugger to continue running with inline error highlights, it violates the Microsoft DAP protocol specification, which requiressuccess: falsewhen a launch request fails to load code.Clients must be explicitly built to handle the non-standard
highlightErrorcustom event to surface parse errors to users; standard DAP clients relying on thesuccessfield will not detect launch failures.
| if not parsed_successfully: | ||
| with contextlib.suppress(RuntimeError): | ||
| server.simulation_state.reset_simulation() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick | 🔵 Trivial
Consider logging the suppressed exception for debuggability.
The contextlib.suppress(RuntimeError) defensively handles the case where reset_simulation() might fail after a failed parse (e.g., if nothing was loaded). While this is reasonable, silently swallowing exceptions can make debugging harder. Consider logging at debug level before suppressing.
💡 Optional: Log before suppressing
+ import logging
+
+ _logger = logging.getLogger(__name__)
+
if not parsed_successfully:
- with contextlib.suppress(RuntimeError):
- server.simulation_state.reset_simulation()
+ try:
+ server.simulation_state.reset_simulation()
+ except RuntimeError:
+ _logger.debug("reset_simulation() failed after parse error (expected)")🤖 Prompt for AI Agents
In @python/mqt/debugger/dap/messages/launch_dap_message.py around lines 78 - 80,
Replace the silent contextlib.suppress around
server.simulation_state.reset_simulation() so that when parsed_successfully is
False any RuntimeError is caught and logged at debug level (including the
exception info) before being ignored; e.g., wrap the call to
server.simulation_state.reset_simulation() in a try/except RuntimeError as e and
call the module or server logger (e.g., logger.debug or server.logger.debug)
with a short message referencing reset_simulation and the caught exception.
| program_path = Path(self.program) | ||
| server.source_file = {"name": program_path.name, "path": self.program} | ||
| parsed_successfully = True | ||
| code = program_path.read_text(encoding=locale.getpreferredencoding(False)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fix Ruff FBT003: avoid boolean positional argument in locale.getpreferredencoding().
Use the keyword form to keep intent clear and satisfy Ruff.
Proposed diff
- code = program_path.read_text(encoding=locale.getpreferredencoding(False))
+ code = program_path.read_text(
+ encoding=locale.getpreferredencoding(do_setlocale=False)
+ )📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| code = program_path.read_text(encoding=locale.getpreferredencoding(False)) | |
| code = program_path.read_text( | |
| encoding=locale.getpreferredencoding(do_setlocale=False) | |
| ) |
🧰 Tools
🪛 Ruff (0.14.10)
70-70: Boolean positional value in function call
(FBT003)
🤖 Prompt for AI Agents
In @python/mqt/debugger/dap/messages/restart_dap_message.py at line 70, The call
to locale.getpreferredencoding uses a boolean positional arg; update the
assignment that reads the file with program_path.read_text to call
locale.getpreferredencoding using the keyword form (e.g.,
locale.getpreferredencoding(do_setlocale=False)) so the intent is explicit and
Ruff FBT003 is satisfied; keep the rest of the code unchanged and only replace
the positional boolean in the code = program_path.read_text(encoding=...) line.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
python/mqt/debugger/dap/messages/variables_dap_message.py (1)
30-31: Inconsistency: Parameters documented and accepted but ignored.The
startandcountparameters are documented as "used for paging" and extracted from the request, but they're overridden with hardcoded values on lines 150-151. If the hardcoded pagination is intentional (which seems unlikely given the PR objectives), these parameters should be removed entirely. However, the correct fix is to revert the changes on lines 150-153.Also applies to: 42-43
🤖 Fix all issues with AI agents
In @python/mqt/debugger/dap/messages/variables_dap_message.py:
- Around line 150-153: The loop hardcodes start and count, ignoring the values
passed to the object/method and breaking pagination; restore use of the actual
parameters instead of hardcoded values by removing the lines that set start = 0
and count = 10, compute num_variables = 2**num_q if count == 0 else count using
the passed-in count, and iterate for i in range(start, start + num_variables) so
the start and count provided via __init__ (and documented in the API) are
honored and the dead `count == 0` branch remains meaningful.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 3
🤖 Fix all issues with AI agents
In @python/mqt/debugger/dap/messages/highlight_error_dap_message.py:
- Around line 139-162: The _normalize_source signature declares source:
Mapping[str, Any] | None but the implementation immediately rejects None; update
the code to make the type and behavior consistent by either removing the
optional union (change the signature to source: Mapping[str, Any]) if callers
never pass None, or explicitly handle None (e.g., accept None and return an
empty normalized dict or raise with a clear message) to mirror the approach used
by _normalize_position; adjust the signature and body of _normalize_source (and
update any __init__ call sites if you choose to require non-None) so the type
hint matches the actual behavior.
- Around line 111-137: The type hint for _normalize_position is inconsistent: it
declares position: Mapping[str, Any] | None but immediately rejects None; remove
the | None from the signature so _normalize_position(position: Mapping[str,
Any]) conveys that callers must validate None first, then update callers (e.g.,
_normalize_highlight and any other callers of _normalize_position) to explicitly
handle a None value before calling (either by raising a clear
ValueError/TypeError or by returning a default), and keep the current internal
checks for Mapping and missing keys as-is to validate non-None inputs.
- Around line 164-179: The validation in _start_comes_after_end currently allows
start == end because it returns (end_line, end_column) < (start_line,
start_column); change the comparison to use <= so the function returns True when
end is less-than-or-equal-to start, matching the error message "Highlight range
'end' must be after 'start' and preventing zero-length ranges; update the return
expression in _start_comes_after_end accordingly.
📜 Review details
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
📒 Files selected for processing (1)
python/mqt/debugger/dap/messages/highlight_error_dap_message.py
🧰 Additional context used
🧬 Code graph analysis (1)
python/mqt/debugger/dap/messages/highlight_error_dap_message.py (2)
python/mqt/debugger/dap/messages/dap_event.py (1)
DAPEvent(17-41)python/mqt/debugger/dap/messages/launch_dap_message.py (1)
validate(44-55)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (15)
- GitHub Check: 🐍 Test (windows-2022) / 🐍 windows-2022
- GitHub Check: 🇨 Test 🐧 (ubuntu-24.04-arm, gcc, Release) / 🐧 ubuntu-24.04-arm gcc Release
- GitHub Check: 🇨 Test 🐧 (ubuntu-24.04, gcc, Debug) / 🐧 ubuntu-24.04 gcc Debug
- GitHub Check: 🇨 Test 🐧 (ubuntu-24.04, gcc, Release) / 🐧 ubuntu-24.04 gcc Release
- GitHub Check: 🐍 Test (ubuntu-24.04-arm) / 🐍 ubuntu-24.04-arm
- GitHub Check: 🐍 Test (macos-15-intel) / 🐍 macos-15-intel
- GitHub Check: 🐍 Test (macos-14) / 🐍 macos-14
- GitHub Check: 🇨 Test 🍎 (macos-14, clang, Debug) / 🍎 macos-14 clang Debug
- GitHub Check: 🇨 Test 🍎 (macos-15-intel, clang, Release) / 🍎 macos-15-intel clang Release
- GitHub Check: 🇨 Test 🍎 (macos-14, clang, Release) / 🍎 macos-14 clang Release
- GitHub Check: 🐍 Test (ubuntu-24.04) / 🐍 ubuntu-24.04
- GitHub Check: 🇨 Test 🏁 (windows-11-arm, msvc, Release) / 🏁 windows-11-arm msvc Release
- GitHub Check: 🇨 Test 🏁 (windows-2022, msvc, Release) / 🏁 windows-2022 msvc Release
- GitHub Check: 🇨 Test 🏁 (windows-2022, msvc, Debug) / 🏁 windows-2022 msvc Debug
- GitHub Check: 🇨 Lint / 🚨 Lint
🔇 Additional comments (6)
python/mqt/debugger/dap/messages/highlight_error_dap_message.py (6)
1-20: LGTM!The module header, imports, and type checking setup follow Python best practices. The use of
TYPE_CHECKINGto conditionally importSequenceavoids unnecessary runtime overhead.
22-34: LGTM!The class definition properly extends
DAPEvent, and the attributes are well-documented with clear type hints.
36-45: LGTM!The constructor properly normalizes inputs before calling
super().__init__(), ensuring validation runs on clean data. This follows the established pattern from other DAP message classes.
47-60: LGTM!The validation logic is sound, ensuring both the presence of highlights and non-empty messages. Error messages are clear and actionable.
62-70: LGTM!The encoding method correctly extends the base class implementation and properly structures the event body according to DAP protocol expectations.
72-109: The concern aboutint()conversion raising ValueError is not applicable to the actual usage pattern.The
instructionfield is always created as an integer in both_build_highlight_entry()and_build_parse_error_highlight()in dap_server.py before being passed to_normalize_highlight(). Since highlight entries originate from internal code (not external/untrusted input), theint()conversion at line 101 is safe and will not encounter non-convertible types in practice.Likely an incorrect or invalid review comment.
| @staticmethod | ||
| def _normalize_source(source: Mapping[str, Any] | None) -> dict[str, Any]: | ||
| """Create a defensive copy of the provided DAP Source information. | ||
| Args: | ||
| source (Mapping[str, Any] | None): The source mapping to normalize. | ||
| Returns: | ||
| dict[str, Any]: Normalized source information with string fields. | ||
| Raises: | ||
| TypeError: If the source is not a mapping. | ||
| ValueError: If required keys are missing. | ||
| """ | ||
| if not isinstance(source, Mapping): | ||
| msg = "Source information must be provided as a mapping." | ||
| raise TypeError(msg) | ||
| normalized = dict(source) | ||
| if "name" not in normalized or "path" not in normalized: | ||
| msg = "Source mappings must at least provide 'name' and 'path'." | ||
| raise ValueError(msg) | ||
| normalized["name"] = str(normalized["name"]) | ||
| normalized["path"] = str(normalized["path"]) | ||
| return normalized |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick | 🔵 Trivial
Similar type hint consideration as _normalize_position.
The function signature includes | None, but the implementation rejects None. Unlike _normalize_position, the caller in __init__ (line 44) passes a non-None source parameter, so this is less likely to be an issue in practice. However, the same type safety considerations apply.
⚙️ Optional: Align type hint with implementation
@staticmethod
- def _normalize_source(source: Mapping[str, Any] | None) -> dict[str, Any]:
+ def _normalize_source(source: Mapping[str, Any]) -> dict[str, Any]:
"""Create a defensive copy of the provided DAP Source information.📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| @staticmethod | |
| def _normalize_source(source: Mapping[str, Any] | None) -> dict[str, Any]: | |
| """Create a defensive copy of the provided DAP Source information. | |
| Args: | |
| source (Mapping[str, Any] | None): The source mapping to normalize. | |
| Returns: | |
| dict[str, Any]: Normalized source information with string fields. | |
| Raises: | |
| TypeError: If the source is not a mapping. | |
| ValueError: If required keys are missing. | |
| """ | |
| if not isinstance(source, Mapping): | |
| msg = "Source information must be provided as a mapping." | |
| raise TypeError(msg) | |
| normalized = dict(source) | |
| if "name" not in normalized or "path" not in normalized: | |
| msg = "Source mappings must at least provide 'name' and 'path'." | |
| raise ValueError(msg) | |
| normalized["name"] = str(normalized["name"]) | |
| normalized["path"] = str(normalized["path"]) | |
| return normalized | |
| @staticmethod | |
| def _normalize_source(source: Mapping[str, Any]) -> dict[str, Any]: | |
| """Create a defensive copy of the provided DAP Source information. | |
| Args: | |
| source (Mapping[str, Any] | None): The source mapping to normalize. | |
| Returns: | |
| dict[str, Any]: Normalized source information with string fields. | |
| Raises: | |
| TypeError: If the source is not a mapping. | |
| ValueError: If required keys are missing. | |
| """ | |
| if not isinstance(source, Mapping): | |
| msg = "Source information must be provided as a mapping." | |
| raise TypeError(msg) | |
| normalized = dict(source) | |
| if "name" not in normalized or "path" not in normalized: | |
| msg = "Source mappings must at least provide 'name' and 'path'." | |
| raise ValueError(msg) | |
| normalized["name"] = str(normalized["name"]) | |
| normalized["path"] = str(normalized["path"]) | |
| return normalized |
🤖 Prompt for AI Agents
In @python/mqt/debugger/dap/messages/highlight_error_dap_message.py around lines
139 - 162, The _normalize_source signature declares source: Mapping[str, Any] |
None but the implementation immediately rejects None; update the code to make
the type and behavior consistent by either removing the optional union (change
the signature to source: Mapping[str, Any]) if callers never pass None, or
explicitly handle None (e.g., accept None and return an empty normalized dict or
raise with a clear message) to mirror the approach used by _normalize_position;
adjust the signature and body of _normalize_source (and update any __init__ call
sites if you choose to require non-None) so the type hint matches the actual
behavior.
This reverts commit 29607af.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 4
🤖 Fix all issues with AI agents
In @python/mqt/debugger/dap/dap_server.py:
- Around line 548-554: The end_column conditional is redundant: since column is
already constrained (column = max(1, column)) and end_column is computed as
end_column = max(column, len(line_text) + 1), the ternary "end_column if
end_column > 0 else column" can be simplified to just end_column; update the
return range end column to use end_column directly (referencing the variables
end_column, column, line_text in dap_server.py) and remove the unnecessary > 0
check.
In @python/mqt/debugger/dap/messages/highlight_error_dap_message.py:
- Around line 180-195: The method _start_comes_after_end currently defensively
uses start.get(..., 0) and end.get(..., 0) which can mask bugs; update the
function to include a brief comment (or a one-line assertion) stating that
callers must pass positions already normalized by _normalize_position() and that
the zero defaults are only a last-resort safety; reference the
_start_comes_after_end function and the earlier call site that normalizes
positions to make the assumption explicit.
- Around line 127-153: The _normalize_position static method currently only
catches KeyError when extracting position["line"] and position["column"], but
int(...) can raise ValueError for non-numeric inputs; update _normalize_position
to also catch ValueError from the int() conversions and re-raise a clear
ValueError (e.g., "Highlight positions require integer 'line' and 'column'.")
using the original exception as the cause so invalid but present values like
"abc" produce a descriptive error.
- Around line 84-125: The normalization currently calls
int(normalized.get("instruction", -1)) which can raise an opaque ValueError;
update HighlightError._normalize_highlight to defensively validate and convert
the "instruction" field: retrieve raw = normalized.get("instruction", -1), allow
ints directly, allow numeric strings (strip and check .isdigit() or use a safe
try/except), and if conversion fails raise a clear TypeError/ValueError with a
descriptive message like "highlight instruction must be an integer or numeric
string" referencing the offending value; implement this conversion before
assigning normalized["instruction"] so other fields remain unchanged.
📜 Review details
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
📒 Files selected for processing (3)
python/mqt/debugger/dap/dap_server.pypython/mqt/debugger/dap/messages/__init__.pypython/mqt/debugger/dap/messages/highlight_error_dap_message.py
🧰 Additional context used
🧬 Code graph analysis (2)
python/mqt/debugger/dap/messages/__init__.py (1)
python/mqt/debugger/dap/messages/highlight_error_dap_message.py (2)
HighlightError(34-195)HighlightReason(20-27)
python/mqt/debugger/dap/messages/highlight_error_dap_message.py (1)
python/mqt/debugger/dap/messages/dap_event.py (1)
DAPEvent(17-41)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (15)
- GitHub Check: 🇨 Test 🍎 (macos-14, clang, Release) / 🍎 macos-14 clang Release
- GitHub Check: 🐍 Test (ubuntu-24.04) / 🐍 ubuntu-24.04
- GitHub Check: 🇨 Test 🍎 (macos-15-intel, clang, Release) / 🍎 macos-15-intel clang Release
- GitHub Check: 🐍 Test (windows-2022) / 🐍 windows-2022
- GitHub Check: 🇨 Test 🏁 (windows-2022, msvc, Release) / 🏁 windows-2022 msvc Release
- GitHub Check: 🐍 Test (macos-15-intel) / 🐍 macos-15-intel
- GitHub Check: 🇨 Test 🐧 (ubuntu-24.04, gcc, Release) / 🐧 ubuntu-24.04 gcc Release
- GitHub Check: 🇨 Test 🏁 (windows-11-arm, msvc, Release) / 🏁 windows-11-arm msvc Release
- GitHub Check: 🐍 Test (ubuntu-24.04-arm) / 🐍 ubuntu-24.04-arm
- GitHub Check: 🇨 Test 🏁 (windows-2022, msvc, Debug) / 🏁 windows-2022 msvc Debug
- GitHub Check: 🐍 Test (macos-14) / 🐍 macos-14
- GitHub Check: 🇨 Test 🍎 (macos-14, clang, Debug) / 🍎 macos-14 clang Debug
- GitHub Check: 🇨 Test 🐧 (ubuntu-24.04, gcc, Debug) / 🐧 ubuntu-24.04 gcc Debug
- GitHub Check: 🇨 Test 🐧 (ubuntu-24.04-arm, gcc, Release) / 🐧 ubuntu-24.04-arm gcc Release
- GitHub Check: 🇨 Lint / 🚨 Lint
🔇 Additional comments (11)
python/mqt/debugger/dap/messages/__init__.py (1)
24-24: LGTM!The new exports follow the existing pattern and maintain alphabetical order in
__all__. The integration is clean and consistent with the module's structure.Also applies to: 61-62
python/mqt/debugger/dap/dap_server.py (10)
172-186: LGTM! This implements the fix for persisting greyed-out highlights.The
_prevent_exitflag is properly reset after stepping/execution commands, ensuring that each execution starts with a clean state. This addresses the bug described in the PR where greyed-out highlights persisted between executions.
257-261: LGTM! Clean implementation of deferred highlight emission.The queueing pattern allows parse errors (or other errors) to be collected during command processing and then emitted after the response. Setting
_prevent_exit = Trueensures the user sees the highlights before the debugger exits.
271-275: LGTM! Exit prevention logic is correct.The additional check for
not self._prevent_exitensures the debugger remains active when highlights are displayed, allowing users to view error information before the session ends.
363-389: LGTM! Edge case handling improvements for position calculations.The changes improve robustness:
- Line 373: Handles empty source code gracefully
- Line 376: Using
<=instead of<correctly handles positions at line boundaries- Lines 381-384: The
elseclause on theforloop handles positions beyond the end of the file, setting them to the last valid positionThese are good defensive programming practices for coordinate conversion.
431-462: LGTM! Robust highlight collection with good fallback logic.The method includes solid defensive programming:
- Line 438: Guards against missing
source_codeattribute- Lines 443-444: Handles potential
RuntimeErrorfrom diagnostics- Lines 453-460: Ensures at least one highlight entry is returned as a fallback
This guarantees users always see error location information, even when detailed diagnostics are unavailable.
476-481: Verify snippet extraction consistency with range boundaries.Lines 476-479 carefully exclude newlines from the display range, setting
end_position_exclusive = end_poswhen the character atend_posis\n. However, line 481 extracts the snippet usingend_pos + 1(always inclusive ofend_pos), which means newlines may be included in the snippet even when excluded from the highlight range.This is likely acceptable for display purposes since line 481 also uses
.replace("\r", ""), but verify that including newlines in the snippet doesn't cause rendering issues in the client.
504-523: LGTM! Parse error handling with good fallback strategy.The regex pattern on line 514 correctly parses compiler error messages in the format
<input>:line:column: detail. The fallback to line 1, column 1 when the pattern doesn't match ensures the function always returns valid coordinates.
537-546: LGTM! Smart UX enhancement for blank line handling.When a parse error points to a blank line at column 1, the code looks backward to find the previous non-blank line and adjusts the highlight accordingly. This provides better visual feedback to users by pointing to the actual code rather than empty space.
561-584: LGTM! Recursive flattening with comprehensive type handling.The method correctly handles nested message structures with strings, dictionaries, and lists. The fallback to
str()conversion for unknown types (line 583) ensures robustness, though consider logging a warning if unexpected types are encountered in debug builds.
586-653: LGTM! Clean refactoring with improved modularity.The introduction of
send_message_simple()as a lower-level helper and the addition of thecategoryparameter tosend_message_hierarchy()improve code reusability and enable proper message categorization (console/stdout/stderr). The type handling in lines 603-612 is careful and defensive.
| end_column = max(column, len(line_text) + 1) | ||
| snippet = line_text.strip() or line_text | ||
| return { | ||
| "instruction": -1, | ||
| "range": { | ||
| "start": {"line": line, "column": column}, | ||
| "end": {"line": line, "column": end_column if end_column > 0 else column}, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick | 🔵 Trivial
Redundant condition check on line 554.
Line 548 computes end_column = max(column, len(line_text) + 1), and earlier (line 533) column = max(1, column) ensures column is at least 1. Therefore, end_column is always ≥ 1, making the check if end_column > 0 else column on line 554 redundant—it will always use end_column.
♻️ Proposed simplification
"range": {
"start": {"line": line, "column": column},
- "end": {"line": line, "column": end_column if end_column > 0 else column},
+ "end": {"line": line, "column": end_column},
},📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| end_column = max(column, len(line_text) + 1) | |
| snippet = line_text.strip() or line_text | |
| return { | |
| "instruction": -1, | |
| "range": { | |
| "start": {"line": line, "column": column}, | |
| "end": {"line": line, "column": end_column if end_column > 0 else column}, | |
| end_column = max(column, len(line_text) + 1) | |
| snippet = line_text.strip() or line_text | |
| return { | |
| "instruction": -1, | |
| "range": { | |
| "start": {"line": line, "column": column}, | |
| "end": {"line": line, "column": end_column}, | |
| }, |
🤖 Prompt for AI Agents
In @python/mqt/debugger/dap/dap_server.py around lines 548 - 554, The end_column
conditional is redundant: since column is already constrained (column = max(1,
column)) and end_column is computed as end_column = max(column, len(line_text) +
1), the ternary "end_column if end_column > 0 else column" can be simplified to
just end_column; update the return range end column to use end_column directly
(referencing the variables end_column, column, line_text in dap_server.py) and
remove the unnecessary > 0 check.
| @staticmethod | ||
| def _normalize_highlight(entry: Mapping[str, Any]) -> dict[str, Any]: | ||
| """Return a shallow copy of a highlight entry with guaranteed structure. | ||
| Args: | ||
| entry (Mapping[str, Any]): Highlight metadata including a range mapping. | ||
| Returns: | ||
| dict[str, Any]: A normalized highlight entry suitable for serialization. | ||
| Raises: | ||
| TypeError: If the range mapping or its positions are not mappings. | ||
| ValueError: If required fields are missing or malformed. | ||
| """ | ||
| if "range" not in entry: | ||
| msg = "A highlight entry must contain a 'range'." | ||
| raise ValueError(msg) | ||
| highlight_range = entry["range"] | ||
| if not isinstance(highlight_range, Mapping): | ||
| msg = "Highlight range must be a mapping with 'start' and 'end'." | ||
| raise TypeError(msg) | ||
|
|
||
| start = HighlightError._normalize_position(highlight_range.get("start")) | ||
| end = HighlightError._normalize_position(highlight_range.get("end")) | ||
| if HighlightError._start_comes_after_end(start, end): | ||
| msg = "Highlight range 'end' must be after 'start'." | ||
| raise ValueError(msg) | ||
|
|
||
| normalized = dict(entry) | ||
| normalized["instruction"] = int(normalized.get("instruction", -1)) | ||
| reason = normalized.get("reason", HighlightReason.UNKNOWN) | ||
| if isinstance(reason, HighlightReason): | ||
| normalized["reason"] = reason.value | ||
| else: | ||
| normalized["reason"] = str(reason) | ||
| normalized["code"] = str(normalized.get("code", "")) | ||
| normalized["message"] = str(normalized.get("message", "")).strip() | ||
| normalized["range"] = { | ||
| "start": start, | ||
| "end": end, | ||
| } | ||
| return normalized |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick | 🔵 Trivial
Consider more defensive type conversion in normalization.
Line 113 performs int(normalized.get("instruction", -1)) which will raise a ValueError if the value cannot be converted to an integer. While this may be intentional for validation purposes, it produces a less descriptive error than the explicit type checks used elsewhere in the class.
♻️ Proposed enhancement for error clarity
normalized = dict(entry)
-normalized["instruction"] = int(normalized.get("instruction", -1))
+try:
+ normalized["instruction"] = int(normalized.get("instruction", -1))
+except (ValueError, TypeError) as exc:
+ msg = "Highlight 'instruction' field must be convertible to an integer."
+ raise ValueError(msg) from exc📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| @staticmethod | |
| def _normalize_highlight(entry: Mapping[str, Any]) -> dict[str, Any]: | |
| """Return a shallow copy of a highlight entry with guaranteed structure. | |
| Args: | |
| entry (Mapping[str, Any]): Highlight metadata including a range mapping. | |
| Returns: | |
| dict[str, Any]: A normalized highlight entry suitable for serialization. | |
| Raises: | |
| TypeError: If the range mapping or its positions are not mappings. | |
| ValueError: If required fields are missing or malformed. | |
| """ | |
| if "range" not in entry: | |
| msg = "A highlight entry must contain a 'range'." | |
| raise ValueError(msg) | |
| highlight_range = entry["range"] | |
| if not isinstance(highlight_range, Mapping): | |
| msg = "Highlight range must be a mapping with 'start' and 'end'." | |
| raise TypeError(msg) | |
| start = HighlightError._normalize_position(highlight_range.get("start")) | |
| end = HighlightError._normalize_position(highlight_range.get("end")) | |
| if HighlightError._start_comes_after_end(start, end): | |
| msg = "Highlight range 'end' must be after 'start'." | |
| raise ValueError(msg) | |
| normalized = dict(entry) | |
| normalized["instruction"] = int(normalized.get("instruction", -1)) | |
| reason = normalized.get("reason", HighlightReason.UNKNOWN) | |
| if isinstance(reason, HighlightReason): | |
| normalized["reason"] = reason.value | |
| else: | |
| normalized["reason"] = str(reason) | |
| normalized["code"] = str(normalized.get("code", "")) | |
| normalized["message"] = str(normalized.get("message", "")).strip() | |
| normalized["range"] = { | |
| "start": start, | |
| "end": end, | |
| } | |
| return normalized | |
| @staticmethod | |
| def _normalize_highlight(entry: Mapping[str, Any]) -> dict[str, Any]: | |
| """Return a shallow copy of a highlight entry with guaranteed structure. | |
| Args: | |
| entry (Mapping[str, Any]): Highlight metadata including a range mapping. | |
| Returns: | |
| dict[str, Any]: A normalized highlight entry suitable for serialization. | |
| Raises: | |
| TypeError: If the range mapping or its positions are not mappings. | |
| ValueError: If required fields are missing or malformed. | |
| """ | |
| if "range" not in entry: | |
| msg = "A highlight entry must contain a 'range'." | |
| raise ValueError(msg) | |
| highlight_range = entry["range"] | |
| if not isinstance(highlight_range, Mapping): | |
| msg = "Highlight range must be a mapping with 'start' and 'end'." | |
| raise TypeError(msg) | |
| start = HighlightError._normalize_position(highlight_range.get("start")) | |
| end = HighlightError._normalize_position(highlight_range.get("end")) | |
| if HighlightError._start_comes_after_end(start, end): | |
| msg = "Highlight range 'end' must be after 'start'." | |
| raise ValueError(msg) | |
| normalized = dict(entry) | |
| try: | |
| normalized["instruction"] = int(normalized.get("instruction", -1)) | |
| except (ValueError, TypeError) as exc: | |
| msg = "Highlight 'instruction' field must be convertible to an integer." | |
| raise ValueError(msg) from exc | |
| reason = normalized.get("reason", HighlightReason.UNKNOWN) | |
| if isinstance(reason, HighlightReason): | |
| normalized["reason"] = reason.value | |
| else: | |
| normalized["reason"] = str(reason) | |
| normalized["code"] = str(normalized.get("code", "")) | |
| normalized["message"] = str(normalized.get("message", "")).strip() | |
| normalized["range"] = { | |
| "start": start, | |
| "end": end, | |
| } | |
| return normalized |
🤖 Prompt for AI Agents
In @python/mqt/debugger/dap/messages/highlight_error_dap_message.py around lines
84 - 125, The normalization currently calls int(normalized.get("instruction",
-1)) which can raise an opaque ValueError; update
HighlightError._normalize_highlight to defensively validate and convert the
"instruction" field: retrieve raw = normalized.get("instruction", -1), allow
ints directly, allow numeric strings (strip and check .isdigit() or use a safe
try/except), and if conversion fails raise a clear TypeError/ValueError with a
descriptive message like "highlight instruction must be an integer or numeric
string" referencing the offending value; implement this conversion before
assigning normalized["instruction"] so other fields remain unchanged.
| @staticmethod | ||
| def _normalize_position(position: Mapping[str, Any] | None) -> dict[str, int]: | ||
| """Normalize a position mapping, ensuring it includes a line and column. | ||
| Args: | ||
| position (Mapping[str, Any] | None): The position mapping to normalize. | ||
| Returns: | ||
| dict[str, int]: A normalized position with integer line and column. | ||
| Raises: | ||
| TypeError: If the provided position is not a mapping. | ||
| ValueError: If required keys are missing. | ||
| """ | ||
| if not isinstance(position, Mapping): | ||
| msg = "Highlight positions must be mappings with 'line' and 'column'." | ||
| raise TypeError(msg) | ||
| try: | ||
| line = int(position["line"]) | ||
| column = int(position["column"]) | ||
| except KeyError as exc: | ||
| msg = "Highlight positions require 'line' and 'column'." | ||
| raise ValueError(msg) from exc | ||
| return { | ||
| "line": line, | ||
| "column": column, | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick | 🔵 Trivial
Consider catching ValueError from int() conversion.
Lines 145-146 use int() to convert position fields, which can raise either KeyError (caught and handled) or ValueError (not caught). If line or column values are present but not convertible to integers (e.g., "abc"), the resulting ValueError will be less descriptive than the explicit TypeError and ValueError messages provided elsewhere.
♻️ Proposed enhancement for comprehensive error handling
try:
line = int(position["line"])
column = int(position["column"])
except KeyError as exc:
msg = "Highlight positions require 'line' and 'column'."
raise ValueError(msg) from exc
+ except (ValueError, TypeError) as exc:
+ msg = "Highlight position 'line' and 'column' must be integers."
+ raise ValueError(msg) from exc🤖 Prompt for AI Agents
In @python/mqt/debugger/dap/messages/highlight_error_dap_message.py around lines
127 - 153, The _normalize_position static method currently only catches KeyError
when extracting position["line"] and position["column"], but int(...) can raise
ValueError for non-numeric inputs; update _normalize_position to also catch
ValueError from the int() conversions and re-raise a clear ValueError (e.g.,
"Highlight positions require integer 'line' and 'column'.") using the original
exception as the cause so invalid but present values like "abc" produce a
descriptive error.
| @staticmethod | ||
| def _start_comes_after_end(start: Mapping[str, Any], end: Mapping[str, Any]) -> bool: | ||
| """Return True if 'start' describes a position after 'end'. | ||
| Args: | ||
| start (Mapping[str, Any]): The start position mapping. | ||
| end (Mapping[str, Any]): The end position mapping. | ||
| Returns: | ||
| bool: True when the start position is after the end position. | ||
| """ | ||
| start_line = int(start.get("line", 0)) | ||
| start_column = int(start.get("column", 0)) | ||
| end_line = int(end.get("line", 0)) | ||
| end_column = int(end.get("column", 0)) | ||
| return (end_line, end_column) < (start_line, start_column) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick | 🔵 Trivial
Defensive use of .get() is safe but could be documented.
Lines 191-194 use .get() with a default value of 0 even though the positions should already be normalized by _normalize_position(). This defensive approach is safe, but the default value of 0 could theoretically mask bugs if somehow un-normalized positions reach this method.
This is not an issue in practice since _start_comes_after_end() is only called after normalization (line 108), but a brief comment explaining this assumption would improve maintainability.
🤖 Prompt for AI Agents
In @python/mqt/debugger/dap/messages/highlight_error_dap_message.py around lines
180 - 195, The method _start_comes_after_end currently defensively uses
start.get(..., 0) and end.get(..., 0) which can mask bugs; update the function
to include a brief comment (or a one-line assertion) stating that callers must
pass positions already normalized by _normalize_position() and that the zero
defaults are only a last-resort safety; reference the _start_comes_after_end
function and the earlier call site that normalizes positions to make the
assumption explicit.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 4
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (3)
python/mqt/debugger/dap/messages/restart_dap_message.py (2)
83-88: Fix: Response command should be "restart", not "launch".The response payload incorrectly specifies
"command": "launch"for a restart request. This should match the actual request type.Proposed fix
return { "type": "response", "request_seq": self.sequence_number, "success": True, - "command": "launch", + "command": "restart", }
46-57: Fix docstring: references wrong class name.The docstring says "Validates the 'LaunchDAPMessage' instance" but this is the
RestartDAPMessageclass.Proposed fix
def validate(self) -> None: - """Validates the 'LaunchDAPMessage' instance. + """Validates the 'RestartDAPMessage' instance. Raises: ValueError: If the 'program' field is missing or the file does not exist. """python/mqt/debugger/__init__.py (1)
47-48: Duplicate entry in__all__list.
"create_ddsim_simulation_state"appears twice on lines 47 and 48.🔧 Proposed fix
"__version__", "check", "create_ddsim_simulation_state", - "create_ddsim_simulation_state", "dap", "destroy_ddsim_simulation_state", ]
🤖 Fix all issues with AI agents
In @bindings/InterfaceBindings.cpp:
- Around line 230-241: The binding for "load_code_with_result" calls
self->loadCodeWithResult without a null check; add a defensive check like the
one used in the "load_code" binding so you verify that the function pointer
(self->loadCodeWithResult) is non-null before calling it, and handle the null
case consistently (e.g., return an appropriate error/empty LoadResult or raise
an exception) — ensure the check aligns with how createDDSimulationState sets
loadCodeWithResult and mirrors the existing getLastErrorMessage/null handling
pattern used elsewhere.
In @include/backend/debug.h:
- Around line 41-65: LoadResult's string fields `detail` and `message` currently
lack ownership/lifetime documentation; update the struct comment to state that
these pointers are owned by the implementation (same semantics as
`getLastErrorMessage`), that they point into internal storage and may be
invalidated by subsequent calls into the library (or by destroying the relevant
context), and that callers must copy the strings if they need them to outlive
future interface calls.
In @src/backend/dd/DDSimDebug.cpp:
- Around line 87-92: The function clearLastError has an accidental recursive
call to itself causing infinite recursion; remove the self-call and instead
perform the intended clearing operations directly: ensure
clearLastError(DDSimulationState* ddsim) does not call clearLastError(ddsim) and
only clears ddsim->lastErrorDetail, sets ddsim->lastErrorLine = 0 and
ddsim->lastErrorColumn = 0 (and reset any other related fields if present) so
the function simply resets the DDSimulationState error state without recursion.
In @src/common/parsing/CodePreprocessing.cpp:
- Around line 78-109: The code duplicates the logic to compute the start of a
line in lineColumnForTarget that already exists in lineColumnForOffset; add a
small helper (e.g., findLineStart(code, offset)) that returns the 0-based index
of the first character of the line containing offset and replace the duplicated
rfind/range logic in both lineColumnForTarget and lineColumnForOffset to call
this helper, keeping behavior identical (return 0 when rfind returns npos,
otherwise rfind+1).
📜 Review details
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
📒 Files selected for processing (12)
bindings/InterfaceBindings.cppinclude/backend/dd/DDSimDebug.hppinclude/backend/debug.hinclude/common/parsing/ParsingError.hpppython/mqt/debugger/__init__.pypython/mqt/debugger/dap/dap_server.pypython/mqt/debugger/dap/messages/launch_dap_message.pypython/mqt/debugger/dap/messages/restart_dap_message.pypython/mqt/debugger/pydebugger.pyisrc/backend/dd/DDSimDebug.cppsrc/common/parsing/CodePreprocessing.cppsrc/common/parsing/ParsingError.cpp
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-11-27T08:52:06.428Z
Learnt from: lsschmid
Repo: munich-quantum-toolkit/qmap PR: 832
File: src/hybridmap/HybridNeutralAtomMapper.cpp:1379-1383
Timestamp: 2025-11-27T08:52:06.428Z
Learning: In src/hybridmap/HybridNeutralAtomMapper.cpp, the getBestMovePos function throws std::runtime_error when no move position is found (finalBestPos.coords is empty). This behavior is acceptable for current use cases since tight architectures are not a concern. A future improvement would be to fall back to SWAP-based strategies instead of throwing.
Applied to files:
src/common/parsing/CodePreprocessing.cppsrc/backend/dd/DDSimDebug.cpp
📚 Learning: 2025-11-05T07:42:45.507Z
Learnt from: burgholzer
Repo: munich-quantum-toolkit/core PR: 1287
File: test/qdmi/dd/error_handling_test.cpp:118-194
Timestamp: 2025-11-05T07:42:45.507Z
Learning: In the munich-quantum-toolkit/core QDMI device API, session parameters can only be set before calling `device_session_init()`. Once a session is initialized, any attempt to set a parameter returns `QDMI_ERROR_BADSTATE`. Since `SessionGuard` (in test/qdmi/dd/helpers/test_utils.hpp) automatically initializes the session in its constructor, tests that need to verify session parameter setting behavior before initialization must allocate a separate uninitialized session rather than reusing the `SessionGuard`'s session.
Applied to files:
src/backend/dd/DDSimDebug.cpp
🧬 Code graph analysis (4)
include/backend/debug.h (2)
include/frontend/cli/CliFrontEnd.hpp (2)
code(46-46)code(68-68)test/utils/common_fixtures.hpp (6)
code(211-215)code(211-213)code(236-244)code(236-237)code(254-256)code(254-254)
python/mqt/debugger/__init__.py (2)
python/mqt/debugger/pydebugger.pyi (2)
LoadResult(106-118)Result(25-31)python/mqt/debugger/check/result_checker.py (1)
Result(37-74)
python/mqt/debugger/dap/messages/restart_dap_message.py (2)
python/mqt/debugger/pydebugger.pyi (2)
load_code_with_result(159-167)Result(25-31)python/mqt/debugger/dap/dap_server.py (1)
queue_parse_error_result(503-510)
python/mqt/debugger/dap/dap_server.py (11)
python/mqt/debugger/dap/messages/next_dap_message.py (1)
NextDAPMessage(21-47)python/mqt/debugger/dap/messages/step_back_dap_message.py (1)
StepBackDAPMessage(21-48)python/mqt/debugger/dap/messages/step_in_dap_message.py (1)
StepInDAPMessage(21-47)python/mqt/debugger/dap/messages/step_out_dap_message.py (1)
StepOutDAPMessage(21-47)python/mqt/debugger/dap/messages/continue_dap_message.py (1)
ContinueDAPMessage(21-49)python/mqt/debugger/dap/messages/reverse_continue_dap_message.py (1)
ReverseContinueDAPMessage(21-49)python/mqt/debugger/dap/messages/restart_frame_dap_message.py (1)
RestartFrameDAPMessage(21-53)python/mqt/debugger/dap/messages/restart_dap_message.py (1)
RestartDAPMessage(26-88)python/mqt/debugger/dap/messages/launch_dap_message.py (1)
LaunchDAPMessage(26-87)python/mqt/debugger/dap/messages/highlight_error_dap_message.py (2)
HighlightError(34-195)encode(74-82)python/mqt/debugger/dap/messages/output_dap_event.py (2)
encode(54-69)OutputDAPEvent(18-69)
🪛 Cppcheck (2.19.0)
src/common/parsing/CodePreprocessing.cpp
[information] 24-24: Include file
(missingIncludeSystem)
[information] 25-25: Include file
(missingIncludeSystem)
[information] 23-23: Include file
(missingIncludeSystem)
[information] 24-24: Include file
(missingIncludeSystem)
[information] 23-23: Include file
(missingIncludeSystem)
[information] 24-24: Include file
(missingIncludeSystem)
[information] 25-25: Include file
(missingIncludeSystem)
[information] 23-23: Include file
(missingIncludeSystem)
[information] 24-24: Include file
(missingIncludeSystem)
[information] 25-25: Include file
(missingIncludeSystem)
src/common/parsing/ParsingError.cpp
[information] 18-18: Include file
(missingIncludeSystem)
[information] 19-19: Include file
(missingIncludeSystem)
[information] 20-20: Include file
(missingIncludeSystem)
[information] 21-21: Include file
(missingIncludeSystem)
[information] 18-18: Include file
(missingIncludeSystem)
[information] 19-19: Include file
(missingIncludeSystem)
[information] 20-20: Include file
(missingIncludeSystem)
[style] 34-34: The function 'location' is never used.
(unusedFunction)
src/backend/dd/DDSimDebug.cpp
[information] 28-28: Include file
(missingIncludeSystem)
[information] 28-28: Include file
(missingIncludeSystem)
🪛 Ruff (0.14.10)
python/mqt/debugger/dap/messages/launch_dap_message.py
71-71: Boolean positional value in function call
(FBT003)
python/mqt/debugger/dap/messages/restart_dap_message.py
72-72: Boolean positional value in function call
(FBT003)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (15)
- GitHub Check: 🐍 Test (ubuntu-24.04-arm) / 🐍 ubuntu-24.04-arm
- GitHub Check: 🇨 Test 🍎 (macos-14, clang, Release) / 🍎 macos-14 clang Release
- GitHub Check: 🇨 Test 🐧 (ubuntu-24.04-arm, gcc, Release) / 🐧 ubuntu-24.04-arm gcc Release
- GitHub Check: 🇨 Test 🍎 (macos-14, clang, Debug) / 🍎 macos-14 clang Debug
- GitHub Check: 🐍 Test (windows-2022) / 🐍 windows-2022
- GitHub Check: 🇨 Test 🏁 (windows-2022, msvc, Release) / 🏁 windows-2022 msvc Release
- GitHub Check: 🇨 Test 🐧 (ubuntu-24.04, gcc, Debug) / 🐧 ubuntu-24.04 gcc Debug
- GitHub Check: 🇨 Test 🏁 (windows-11-arm, msvc, Release) / 🏁 windows-11-arm msvc Release
- GitHub Check: 🇨 Test 🍎 (macos-15-intel, clang, Release) / 🍎 macos-15-intel clang Release
- GitHub Check: 🐍 Test (macos-14) / 🐍 macos-14
- GitHub Check: 🇨 Test 🐧 (ubuntu-24.04, gcc, Release) / 🐧 ubuntu-24.04 gcc Release
- GitHub Check: 🇨 Test 🏁 (windows-2022, msvc, Debug) / 🏁 windows-2022 msvc Debug
- GitHub Check: 🐍 Test (ubuntu-24.04) / 🐍 ubuntu-24.04
- GitHub Check: 🐍 Test (macos-15-intel) / 🐍 macos-15-intel
- GitHub Check: 🇨 Lint / 🚨 Lint
🔇 Additional comments (30)
src/common/parsing/CodePreprocessing.cpp (9)
23-25: LGTM!The new includes are appropriate:
<cctype>forstd::isdigitand<exception>for the catch blocks usingstd::exception. The static analysis hints about missing system includes are false positives.
38-49: LGTM!The
isDigitshelper is well-implemented. Theunsigned charcast is the correct approach forstd::isdigitto avoid undefined behavior with negative char values.
56-76: LGTM!The line/column calculation is correct. The O(n) newline counting is acceptable since this is only invoked during error reporting.
111-151: LGTM!The error-building helper functions are well-structured and provide clear, descriptive error messages with location information.
153-203: LGTM!The
validateTargetsfunction is robust with comprehensive bracket format validation (line 176) and proper bounds checking. The defensive try-catch aroundstoul(lines 187-192) after theisDigitscheck is good practice for handling potential overflow of very large numbers.
504-508: LGTM!The improved
trueStartcalculation correctly skips leading whitespace to provide more accurate error locations, while properly handling edge cases with the bounds check.
534-546: LGTM!The strengthened register declaration validation provides better error handling with descriptive messages. The defensive try-catch around
stoulafterisDigitsvalidation correctly handles potential overflow edge cases.
610-611: LGTM!Good integration of the centralized target validation for assertions with context-specific error messages.
616-619: LGTM!Correctly validates targets for non-declaration instructions while appropriately skipping variable declarations which are handled separately.
python/mqt/debugger/dap/messages/launch_dap_message.py (1)
68-87: LGTM! Proper error handling flow for parse failures.The new implementation correctly:
- Sets source file metadata before loading
- Uses
load_code_with_resultfor structured error reporting- Queues parse errors for later highlighting
- Conditionally runs simulation only on success
- Resets simulation on failure (suppressing potential RuntimeError)
The response always returns
success: Truewhich is appropriate per DAP protocol—parse errors are communicated via separate events rather than failing the launch response.include/backend/dd/DDSimDebug.hpp (2)
122-137: LGTM! Well-documented error-tracking fields.The new error-tracking members follow the existing conventions:
- Clear documentation with 1-based line/column semantics and 0 indicating unavailable
- Default initialization for line/column
- Proper placement alongside other state fields
299-306: LGTM! New API follows existing patterns.The
ddsimLoadCodeWithResultfunction declaration is consistent with the existingddsimLoadCodeAPI and provides structured error reporting viaLoadResult, which is properly defined ininclude/backend/debug.hwith fields for error result, location (line/column), and detailed error information.include/common/parsing/ParsingError.hpp (1)
28-57: LGTM! Clean optional location design.The
ParsingErrorLocationstruct and its integration intoParsingErroris well-designed:
- Using
std::optionalappropriately for potentially absent location data- Exposing via
constpointer withnoexceptaccessor- Taking
std::stringby value in the constructor enables move semanticspython/mqt/debugger/dap/dap_server.py (7)
171-185: LGTM! Proper exit prevention flow.The
_prevent_exitflag correctly ensures that the debugger doesn't immediately exit after emitting parse error highlights, giving the client time to display the error state.
362-388: LGTM! Improved edge case handling in coordinate conversion.The updates properly handle:
- Empty source code (returns line 1)
- Positions beyond source code length (returns last line/column)
- Proper 0/1-based indexing adjustments
The
elseclause after theforloop (lines 380-383) correctly handles positions that exceed the total source code length by returning the last valid position.
430-501: LGTM! Robust highlight entry collection.The implementation:
- Defensively checks for
source_codeexistence- Properly handles end position for newline characters
- Falls back to highlighting the failing instruction if no error causes found
- Maps error cause types to appropriate highlight reasons
503-546: LGTM! Smart parse error location heuristic.The
_build_parse_error_highlightmethod includes a useful heuristic (lines 524-533) that adjusts the highlight location when the parser reports an error on an empty line—it looks back to find the previous non-empty line, which typically contains the actual error. This improves user experience for common parser error reporting patterns.Using
instruction: -1clearly indicates this is a parse-time error, not associated with any instruction.
548-571: LGTM! Clean recursive message flattening.The method properly handles the nested message structure with support for:
- Plain strings
- Dictionaries with title/body/end keys
- Nested lists
- Fallback to string conversion for other types
573-640: LGTM! Clean separation of message handling concerns.The refactoring:
- Adds
categoryparameter for output type control (console/stdout/stderr)- Extracts message assembly into
send_message_simple- Uses
_flatten_message_partsfor recursive structure handling- Properly handles empty message case with early return
354-360: LGTM! Assertion failure highlighting integration.The highlight entries are properly collected and emitted after the existing gray-out and output messages, providing structured error location data to the client.
python/mqt/debugger/pydebugger.pyi (1)
25-31: LGTM!The new
Resultenum,LoadResultclass, andload_code_with_resultmethod are well-defined with appropriate documentation. The type stubs correctly mirror the C++ interface definitions.Also applies to: 106-118, 159-167
include/backend/debug.h (1)
83-100: LGTM!The new
loadCodeWithResultandgetLastErrorMessagefunction pointers are well-documented with clear ownership semantics for the returned string pointer.src/common/parsing/ParsingError.cpp (1)
26-39: LGTM!The new constructor correctly builds a formatted error message with location information, and the
location()accessor properly handles the optional value. The static analysis warning aboutlocation()being unused is a false positive—it's called fromDDSimDebug.cppat line 636.bindings/InterfaceBindings.cpp (2)
77-83: LGTM!The
Resultenum binding correctly exposesOKandERRORvalues to Python.
177-204: LGTM!The
LoadResultbinding correctly handles nullable C string pointers by returningpy::none()when they are null, preventing null pointer issues in Python.src/backend/dd/DDSimDebug.cpp (4)
79-85: LGTM!The
ddsimGetLastErrorMessageimplementation correctly returnsnullptrfor empty messages and a C-string pointer otherwise.
605-668: LGTM!The
ddsimLoadCodeInternalfunction properly handles different exception types, extracting location information fromParsingErrorand providing fallback messages for other exceptions. The error state is correctly populated for the new structured error reporting.
670-685: LGTM!The
ddsimLoadCodeandddsimLoadCodeWithResultwrappers correctly delegate to the internal implementation and construct the appropriate return types.
1461-1465: The breakpoint insertion behavior is correct and intentional.When
desiredPositionfalls before an instruction's start position, the breakpoint is placed at that instruction (round-up behavior). This is verified by the existing test cases that validate both valid positions frombreakpointPositionsand out-of-bounds cases (expectingERRORwhendesiredPositionexceeds all instructions). All callers (CLI, Python bindings, DAP handler) properly handle the returned result and actual position.python/mqt/debugger/__init__.py (1)
38-39: No action needed. TheResultenum frompydebuggerand theResultclass fromcheck.result_checkerare in different module namespaces (mqt.debugger.Resultvsmqt.debugger.check.Result) and do not create a naming conflict. Users will access them through different import paths.Likely an incorrect or invalid review comment.
bindings/InterfaceBindings.cpp
Outdated
| .def( | ||
| "load_code_with_result", | ||
| [](SimulationState* self, const char* code) { | ||
| return self->loadCodeWithResult(self, code); | ||
| }, | ||
| R"(Loads the given code into the simulation state and returns details. | ||
| Args: | ||
| code (str): The code to load. | ||
| Returns: | ||
| LoadResult: The structured load result.)") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick | 🔵 Trivial
Consider adding a null check for loadCodeWithResult.
Unlike load_code which defensively checks getLastErrorMessage for null (line 216-217), load_code_with_result calls self->loadCodeWithResult without verification. While this function pointer is set in createDDSimulationState, adding a defensive check would maintain consistency.
💡 Suggested defensive check
.def(
"load_code_with_result",
[](SimulationState* self, const char* code) {
+ if (self->loadCodeWithResult == nullptr) {
+ throw std::runtime_error("loadCodeWithResult not implemented");
+ }
return self->loadCodeWithResult(self, code);
},📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| .def( | |
| "load_code_with_result", | |
| [](SimulationState* self, const char* code) { | |
| return self->loadCodeWithResult(self, code); | |
| }, | |
| R"(Loads the given code into the simulation state and returns details. | |
| Args: | |
| code (str): The code to load. | |
| Returns: | |
| LoadResult: The structured load result.)") | |
| .def( | |
| "load_code_with_result", | |
| [](SimulationState* self, const char* code) { | |
| if (self->loadCodeWithResult == nullptr) { | |
| throw std::runtime_error("loadCodeWithResult not implemented"); | |
| } | |
| return self->loadCodeWithResult(self, code); | |
| }, | |
| R"(Loads the given code into the simulation state and returns details. | |
| Args: | |
| code (str): The code to load. | |
| Returns: | |
| LoadResult: The structured load result.)") |
🤖 Prompt for AI Agents
In @bindings/InterfaceBindings.cpp around lines 230 - 241, The binding for
"load_code_with_result" calls self->loadCodeWithResult without a null check; add
a defensive check like the one used in the "load_code" binding so you verify
that the function pointer (self->loadCodeWithResult) is non-null before calling
it, and handle the null case consistently (e.g., return an appropriate
error/empty LoadResult or raise an exception) — ensure the check aligns with how
createDDSimulationState sets loadCodeWithResult and mirrors the existing
getLastErrorMessage/null handling pattern used elsewhere.
include/backend/debug.h
Outdated
| /** | ||
| * @brief Represents the structured result of a load operation. | ||
| */ | ||
| typedef struct { | ||
| /** | ||
| * @brief The result of the load operation. | ||
| */ | ||
| Result result; | ||
| /** | ||
| * @brief The 1-based line of the error location, or 0 if unavailable. | ||
| */ | ||
| size_t line; | ||
| /** | ||
| * @brief The 1-based column of the error location, or 0 if unavailable. | ||
| */ | ||
| size_t column; | ||
| /** | ||
| * @brief The error detail text, or nullptr if unavailable. | ||
| */ | ||
| const char* detail; | ||
| /** | ||
| * @brief The full error message, or nullptr if unavailable. | ||
| */ | ||
| const char* message; | ||
| } LoadResult; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick | 🔵 Trivial
Document pointer lifetime semantics for LoadResult string fields.
The detail and message pointers in LoadResult are owned by the implementation (similar to getLastErrorMessage), but this isn't documented. Since LoadResult is returned by value, callers need to know these pointers may become invalid after subsequent interface calls.
📝 Suggested documentation enhancement
/**
* @brief The error detail text, or nullptr if unavailable.
+ *
+ * The returned pointer is owned by the implementation and remains valid
+ * until the next interface call that modifies the error state.
*/
const char* detail;
/**
* @brief The full error message, or nullptr if unavailable.
+ *
+ * The returned pointer is owned by the implementation and remains valid
+ * until the next interface call that modifies the error state.
*/
const char* message;
} LoadResult;📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| /** | |
| * @brief Represents the structured result of a load operation. | |
| */ | |
| typedef struct { | |
| /** | |
| * @brief The result of the load operation. | |
| */ | |
| Result result; | |
| /** | |
| * @brief The 1-based line of the error location, or 0 if unavailable. | |
| */ | |
| size_t line; | |
| /** | |
| * @brief The 1-based column of the error location, or 0 if unavailable. | |
| */ | |
| size_t column; | |
| /** | |
| * @brief The error detail text, or nullptr if unavailable. | |
| */ | |
| const char* detail; | |
| /** | |
| * @brief The full error message, or nullptr if unavailable. | |
| */ | |
| const char* message; | |
| } LoadResult; | |
| /** | |
| * @brief Represents the structured result of a load operation. | |
| */ | |
| typedef struct { | |
| /** | |
| * @brief The result of the load operation. | |
| */ | |
| Result result; | |
| /** | |
| * @brief The 1-based line of the error location, or 0 if unavailable. | |
| */ | |
| size_t line; | |
| /** | |
| * @brief The 1-based column of the error location, or 0 if unavailable. | |
| */ | |
| size_t column; | |
| /** | |
| * @brief The error detail text, or nullptr if unavailable. | |
| * | |
| * The returned pointer is owned by the implementation and remains valid | |
| * until the next interface call that modifies the error state. | |
| */ | |
| const char* detail; | |
| /** | |
| * @brief The full error message, or nullptr if unavailable. | |
| * | |
| * The returned pointer is owned by the implementation and remains valid | |
| * until the next interface call that modifies the error state. | |
| */ | |
| const char* message; | |
| } LoadResult; |
🤖 Prompt for AI Agents
In @include/backend/debug.h around lines 41 - 65, LoadResult's string fields
`detail` and `message` currently lack ownership/lifetime documentation; update
the struct comment to state that these pointers are owned by the implementation
(same semantics as `getLastErrorMessage`), that they point into internal storage
and may be invalidated by subsequent calls into the library (or by destroying
the relevant context), and that callers must copy the strings if they need them
to outlive future interface calls.
src/backend/dd/DDSimDebug.cpp
Outdated
| void clearLastError(DDSimulationState* ddsim) { | ||
| clearLastError(ddsim); | ||
| ddsim->lastErrorDetail.clear(); | ||
| ddsim->lastErrorLine = 0; | ||
| ddsim->lastErrorColumn = 0; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Critical: Infinite recursion in clearLastError.
The function calls itself recursively on line 88, causing a stack overflow when invoked. This will crash the application when loading code (called at line 627).
🐛 Proposed fix
void clearLastError(DDSimulationState* ddsim) {
- clearLastError(ddsim);
+ ddsim->lastErrorMessage.clear();
ddsim->lastErrorDetail.clear();
ddsim->lastErrorLine = 0;
ddsim->lastErrorColumn = 0;
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| void clearLastError(DDSimulationState* ddsim) { | |
| clearLastError(ddsim); | |
| ddsim->lastErrorDetail.clear(); | |
| ddsim->lastErrorLine = 0; | |
| ddsim->lastErrorColumn = 0; | |
| } | |
| void clearLastError(DDSimulationState* ddsim) { | |
| ddsim->lastErrorMessage.clear(); | |
| ddsim->lastErrorDetail.clear(); | |
| ddsim->lastErrorLine = 0; | |
| ddsim->lastErrorColumn = 0; | |
| } |
🤖 Prompt for AI Agents
In @src/backend/dd/DDSimDebug.cpp around lines 87 - 92, The function
clearLastError has an accidental recursive call to itself causing infinite
recursion; remove the self-call and instead perform the intended clearing
operations directly: ensure clearLastError(DDSimulationState* ddsim) does not
call clearLastError(ddsim) and only clears ddsim->lastErrorDetail, sets
ddsim->lastErrorLine = 0 and ddsim->lastErrorColumn = 0 (and reset any other
related fields if present) so the function simply resets the DDSimulationState
error state without recursion.
| /** | ||
| * @brief Compute the 1-based line and column for a target within a line. | ||
| * @param code The source code to inspect. | ||
| * @param instructionStart The zero-based offset of the instruction start. | ||
| * @param target The target token to locate on the line. | ||
| * @return The line and column of the target, or the first non-space column. | ||
| */ | ||
| LineColumn lineColumnForTarget(const std::string& code, size_t instructionStart, | ||
| const std::string& target) { | ||
| LineColumn location = lineColumnForOffset(code, instructionStart); | ||
| const auto lineStartPos = code.rfind('\n', instructionStart); | ||
| const size_t lineStart = (lineStartPos == std::string::npos) | ||
| ? 0 | ||
| : static_cast<size_t>(lineStartPos + 1); | ||
| auto lineEndPos = code.find('\n', instructionStart); | ||
| const size_t lineEnd = (lineEndPos == std::string::npos) | ||
| ? code.size() | ||
| : static_cast<size_t>(lineEndPos); | ||
| const auto lineText = code.substr(lineStart, lineEnd - lineStart); | ||
| if (!target.empty()) { | ||
| const auto targetPos = lineText.find(target); | ||
| if (targetPos != std::string::npos) { | ||
| location.column = targetPos + 1; | ||
| return location; | ||
| } | ||
| } | ||
| const auto nonSpace = lineText.find_first_not_of(" \t"); | ||
| if (nonSpace != std::string::npos) { | ||
| location.column = nonSpace + 1; | ||
| } | ||
| return location; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick | 🔵 Trivial
Consider extracting lineStart calculation to reduce duplication.
The lineStart calculation logic (lines 88-91) is duplicated from lineColumnForOffset (lines 64-67). Consider extracting a small helper function to compute lineStart from an offset.
♻️ Suggested helper extraction
/**
* @brief Find the start position of the line containing the given offset.
*/
size_t findLineStart(const std::string& code, size_t offset) {
const auto lineStartPos = code.rfind('\n', offset);
return (lineStartPos == std::string::npos) ? 0 : lineStartPos + 1;
}Then use in both functions:
LineColumn lineColumnForOffset(const std::string& code, size_t offset) {
LineColumn location;
- const auto lineStartPos = code.rfind('\n', offset);
- const size_t lineStart = (lineStartPos == std::string::npos)
- ? 0
- : static_cast<size_t>(lineStartPos + 1);
+ const size_t lineStart = findLineStart(code, offset);🤖 Prompt for AI Agents
In @src/common/parsing/CodePreprocessing.cpp around lines 78 - 109, The code
duplicates the logic to compute the start of a line in lineColumnForTarget that
already exists in lineColumnForOffset; add a small helper (e.g.,
findLineStart(code, offset)) that returns the 0-based index of the first
character of the line containing offset and replace the duplicated rfind/range
logic in both lineColumnForTarget and lineColumnForOffset to call this helper,
keeping behavior identical (return 0 when rfind returns npos, otherwise
rfind+1).
This reverts commit 15bb5d9.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 4
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
python/mqt/debugger/dap/messages/restart_dap_message.py (1)
82-87: Change response"command"from"launch"to"restart"to comply with DAP protocol.According to the DAP specification, a Response message's
"command"field must be the name of the request being replied to. Since this handler processes a"restart"request, the response must echo that with"command": "restart", not"command": "launch". This is a protocol violation that will cause client-side issues.Fix
return { "type": "response", "request_seq": self.sequence_number, "success": True, - "command": "launch", + "command": "restart", }
🤖 Fix all issues with AI agents
In @src/backend/dd/DDSimDebug.cpp:
- Around line 1152-1153: The local pointer ddsim created by calling
toDDSimulationState(self) inside ddsimRunAll is unused; remove the unused
variable declaration (the call to toDDSimulationState and the auto* ddsim
assignment) from ddsimRunAll to avoid the unused-variable warning, or if the
underlying DDSimulationState is actually needed later, replace usages of self
with ddsim where appropriate; ensure you keep the function signature and
behavior intact.
In @src/common/parsing/CodePreprocessing.cpp:
- Around line 97-103: The current substring search using lineText.find(target)
can match inside other tokens (e.g., "q" in "qreg"); update the search to find a
whole-token match instead: scan for occurrences of target in lineText and verify
character boundaries (or use a regex with word boundaries like "\\btarget\\b")
so the found position corresponds to a standalone token; change the logic around
target, lineText, targetPos and location.column to use this token-aware check
before assigning location.column and returning.
- Around line 62-76: The function LineColumn lineColumnForOffset has an
off-by-one edge when offset points at a '\n' because rfind('\n', offset) can
return offset itself, producing column 0; fix by ensuring rfind searches before
the offset (e.g., use rfind('\n', offset - 1) when offset > 0 or otherwise
adjust the search) and recompute lineStart/column accordingly so column remains
1-based; update the logic inside lineColumnForOffset to handle offset==0 and
offset pointing at a newline (or alternatively clamp offset to the previous
byte) and keep all callers' semantics unchanged.
- Around line 184-196: The code redundantly checks isDigits(indexText) then
wraps std::stoul in a broad try/catch; consolidate by keeping the isDigits check
and replacing the try/catch with a deterministic range check: parse with
std::stoull (or use a checked conversion), compare the resulting unsigned long
long against std::numeric_limits<size_t>::max(), and if it exceeds that limit
throw the same ParsingError (using formatParseError/invalidTargetDetail/target);
otherwise assign the value to registerIndex (static_cast<size_t>) — this removes
the broad exception handling while preserving correctness for out-of-range
values.
📜 Review details
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
📒 Files selected for processing (8)
bindings/InterfaceBindings.cppinclude/backend/dd/DDSimDebug.hppinclude/backend/debug.hpython/mqt/debugger/dap/dap_server.pypython/mqt/debugger/dap/messages/launch_dap_message.pypython/mqt/debugger/dap/messages/restart_dap_message.pysrc/backend/dd/DDSimDebug.cppsrc/common/parsing/CodePreprocessing.cpp
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-11-05T07:42:45.507Z
Learnt from: burgholzer
Repo: munich-quantum-toolkit/core PR: 1287
File: test/qdmi/dd/error_handling_test.cpp:118-194
Timestamp: 2025-11-05T07:42:45.507Z
Learning: In the munich-quantum-toolkit/core QDMI device API, session parameters can only be set before calling `device_session_init()`. Once a session is initialized, any attempt to set a parameter returns `QDMI_ERROR_BADSTATE`. Since `SessionGuard` (in test/qdmi/dd/helpers/test_utils.hpp) automatically initializes the session in its constructor, tests that need to verify session parameter setting behavior before initialization must allocate a separate uninitialized session rather than reusing the `SessionGuard`'s session.
Applied to files:
src/backend/dd/DDSimDebug.cpp
📚 Learning: 2025-11-27T08:52:06.428Z
Learnt from: lsschmid
Repo: munich-quantum-toolkit/qmap PR: 832
File: src/hybridmap/HybridNeutralAtomMapper.cpp:1379-1383
Timestamp: 2025-11-27T08:52:06.428Z
Learning: In src/hybridmap/HybridNeutralAtomMapper.cpp, the getBestMovePos function throws std::runtime_error when no move position is found (finalBestPos.coords is empty). This behavior is acceptable for current use cases since tight architectures are not a concern. A future improvement would be to fall back to SWAP-based strategies instead of throwing.
Applied to files:
src/backend/dd/DDSimDebug.cppsrc/common/parsing/CodePreprocessing.cpp
🔇 Additional comments (31)
src/common/parsing/CodePreprocessing.cpp (5)
43-49: LGTM!The
isDigitsimplementation correctly handles the empty string case and properly casts tounsigned charbefore callingstd::isdigit, avoiding undefined behavior with potentially negativecharvalues.
119-125: LGTM!The error formatting follows a standard compiler-style format (
<input>:line:col: message) which is helpful for IDE integration and user readability.
509-513: LGTM!The
trueStartcalculation correctly identifies the first non-whitespace character, improving error location accuracy by pointing to actual code rather than leading whitespace.
539-551: LGTM!The register parsing now properly validates that the size is a valid numeric string before parsing, providing clear error messages with location information. This is consistent with the validation approach used in
validateTargets.
615-624: LGTM!The centralized
validateTargetscalls provide consistent validation for both assertion targets and regular instruction targets, with appropriate context strings for clearer error messages. The guard at line 621 correctly excludes variable declarations from target validation.python/mqt/debugger/dap/messages/restart_dap_message.py (3)
13-13: LGTM!The
contextlibimport is appropriately added to support thecontextlib.suppress()usage in the error handling cleanup path.
68-69: LGTM!Good additions: setting
server.source_fileensures the server tracks the current source, and theparsed_successfullyflag provides clean state tracking for the parse outcome.
72-81: LGTM!Solid error handling pattern:
- Catching
RuntimeErrorfromload_codeand queuing the error for separate display is clean.- Using
contextlib.suppress(RuntimeError)for cleanup is appropriately defensive since reset may also fail if parsing failed.- The conditional guards correctly prevent running the simulation when parsing fails.
include/backend/dd/DDSimDebug.hpp (1)
122-125: LGTM!The
lastErrorMessagefield is well-documented and appropriately placed within the struct. The defaultstd::stringinitialization to an empty string aligns with the error-checking logic inddsimGetLastErrorMessagewhich returnsnullptrwhen the message is empty.python/mqt/debugger/dap/messages/launch_dap_message.py (1)
67-80: LGTM!The refactored error handling flow is well-structured:
- Setting
source_fileearly ensures highlight events can reference the correct file- Queuing parse errors instead of returning an error response integrates with the new highlighting mechanism
- The
contextlib.suppressfor reset is appropriate since the simulation may be in an inconsistent state after a parse failureThe pattern of returning
success=Truewhile queuing errors for later display viaHighlightErrorevents is consistent with the DAP server's new highlighting architecture.python/mqt/debugger/dap/dap_server.py (13)
118-119: LGTM!The new state fields are appropriately initialized and typed. The
_prevent_exitflag ensures thatExitedDAPEventis not emitted prematurely when highlight events are pending.
172-186: LGTM!Resetting
_prevent_exitbefore processing execution commands ensures the flag doesn't persist across user actions, allowing properExitedDAPEventemission when appropriate.
257-261: LGTM!The pending highlights emission logic correctly clears the queue after sending and sets
_prevent_exitto prevent premature exit events while the user views the highlights.
271-275: LGTM!The
_prevent_exitcheck correctly gates theExitedDAPEventemission, ensuring users can view error highlights before the debugging session terminates.
355-361: LGTM!The assertion failure handling correctly categorizes the output as
stderrand efficiently reuses the already-fetchederror_causesfor building highlight entries.
373-384: LGTM!The edge case handling improvements are well-considered:
- Empty source code returns
(0, ...)instead of potentially incorrect values- The
<=check allows positions at the end of a line (e.g., after the last character)- The
elseclause gracefully handles positions beyond the source code length by returning the last valid position
431-462: LGTM!The
collect_highlight_entriesmethod is well-designed with:
- Defensive
getattrcheck forsource_codeavailability- Exception handling around diagnostics calls
- A fallback to highlight the failing instruction when no specific error causes are found
464-491: LGTM!The
_build_highlight_entrymethod correctly handles:
- Error recovery via
try/exceptreturningNone- End position edge cases for newline characters
- Bounds checking with
min()to prevent out-of-range access- Code snippet extraction with whitespace trimming
493-502: LGTM!Clean static mapping method with appropriate fallback to
UNKNOWNfor unrecognized error cause types.
504-523: LGTM!The parse error handling correctly extracts source location from compiler error messages using a regex pattern. The fallback to
(1, 1)for unrecognized formats ensures the error is still highlighted.
525-559: LGTM!Excellent edge case handling for parse error highlighting:
- Line/column clamping prevents out-of-bounds issues
- The backward search for empty lines improves UX by highlighting the actual problematic code rather than an empty line
- Using
instruction=-1as a sentinel for parse errors (no associated instruction) is a clean design choice
561-584: LGTM!The
_flatten_message_partsmethod comprehensively handles nested message structures with appropriate type checking and recursion. The filtering of empty strings ensures clean output.
586-653: LGTM!The refactored message handling:
- Adds the
categoryparameter to supportstderr/stdout/consoledifferentiation- Cleanly separates message assembly (
send_message_hierarchy) from emission (send_message_simple)- Properly flattens nested message structures before concatenation
bindings/InterfaceBindings.cpp (1)
177-187: LGTM!The enhanced error handling provides significantly better user experience:
- Defensively checks if
getLastErrorMessageis available before calling- Handles
nullptrreturn gracefully via the ternary operator- Falls back to a generic message if the specific error message is empty
- The
std::runtime_erroris correctly caught asRuntimeErrorin Python, aligning with the DAP server's exception handlinginclude/backend/debug.h (1)
57-66: LGTM!The
getLastErrorMessagefunction pointer is well-documented with clear ownership semantics. The note about pointer validity until the next error-state-modifying call is important for callers to understand the lifetime constraints.src/backend/dd/DDSimDebug.cpp (6)
78-84: LGTM!The implementation correctly returns
nullptrfor empty messages and provides a pointer to the internal string storage viac_str(). The lifetime matches the documented behavior since the pointer remains valid until the next call modifieslastErrorMessage.
524-524: LGTM!The function pointer is correctly assigned to complete the interface wiring.
584-584: LGTM!Clearing
lastErrorMessageduring initialization ensures a clean state for each session.
603-619: LGTM!The comprehensive state clearing ensures a clean slate before loading new code. Setting
ready = falseearly prevents any operations from proceeding on stale state during the load process.
621-636: LGTM!The error handling is robust:
- Captures the exception message via
what()with a fallback for empty messages- The catch-all handler ensures unexpected exception types don't crash and still produce an error
- Storing the message rather than printing to
stderrenables proper error propagation to the frontend
1424-1428: Manual verification required for breakpoint edge case handling.The review requests verification of edge case behavior when users click in whitespace or comments before code (positions before the first instruction). Specifically, confirm that:
- Positions within file headers/comments before any instructions are handled correctly
- The very first character position (0) produces expected behavior
- Existing tests adequately cover these edge cases
This verification could not be completed automatically due to repository access issues.
| Result ddsimRunAll(SimulationState* self, size_t* failedAssertions) { | ||
| auto* ddsim = toDDSimulationState(self); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick | 🔵 Trivial
Unused variable.
The ddsim pointer is obtained but not used in the ddsimRunAll function. The function only uses self for interface calls.
♻️ Suggested fix
Result ddsimRunAll(SimulationState* self, size_t* failedAssertions) {
- auto* ddsim = toDDSimulationState(self);
size_t errorCount = 0;📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| Result ddsimRunAll(SimulationState* self, size_t* failedAssertions) { | |
| auto* ddsim = toDDSimulationState(self); | |
| Result ddsimRunAll(SimulationState* self, size_t* failedAssertions) { | |
| size_t errorCount = 0; |
🤖 Prompt for AI Agents
In @src/backend/dd/DDSimDebug.cpp around lines 1152 - 1153, The local pointer
ddsim created by calling toDDSimulationState(self) inside ddsimRunAll is unused;
remove the unused variable declaration (the call to toDDSimulationState and the
auto* ddsim assignment) from ddsimRunAll to avoid the unused-variable warning,
or if the underlying DDSimulationState is actually needed later, replace usages
of self with ddsim where appropriate; ensure you keep the function signature and
behavior intact.
| LineColumn lineColumnForOffset(const std::string& code, size_t offset) { | ||
| LineColumn location; | ||
| const auto lineStartPos = code.rfind('\n', offset); | ||
| const size_t lineStart = (lineStartPos == std::string::npos) | ||
| ? 0 | ||
| : static_cast<size_t>(lineStartPos + 1); | ||
| location.line = 1; | ||
| for (size_t i = 0; i < lineStart; i++) { | ||
| if (code[i] == '\n') { | ||
| location.line++; | ||
| } | ||
| } | ||
| location.column = offset - lineStart + 1; | ||
| return location; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Potential edge case when offset points at a newline character.
When offset points directly at a \n, rfind('\n', offset) returns offset itself. This means lineStart = offset + 1, and column = offset - (offset + 1) + 1 = 0, which would be incorrect (column should be 1-based).
Consider whether this edge case can occur in practice, or add a guard:
Suggested fix
LineColumn lineColumnForOffset(const std::string& code, size_t offset) {
LineColumn location;
+ // Clamp offset to valid range
+ if (offset > 0 && offset <= code.size() && code[offset - 1] == '\n') {
+ // If we're at the start of a line, adjust to report column 1
+ }
const auto lineStartPos = code.rfind('\n', offset);
const size_t lineStart = (lineStartPos == std::string::npos)
? 0
: static_cast<size_t>(lineStartPos + 1);Alternatively, ensure callers never pass an offset pointing to a newline.
🤖 Prompt for AI Agents
In @src/common/parsing/CodePreprocessing.cpp around lines 62 - 76, The function
LineColumn lineColumnForOffset has an off-by-one edge when offset points at a
'\n' because rfind('\n', offset) can return offset itself, producing column 0;
fix by ensuring rfind searches before the offset (e.g., use rfind('\n', offset -
1) when offset > 0 or otherwise adjust the search) and recompute
lineStart/column accordingly so column remains 1-based; update the logic inside
lineColumnForOffset to handle offset==0 and offset pointing at a newline (or
alternatively clamp offset to the previous byte) and keep all callers' semantics
unchanged.
| if (!target.empty()) { | ||
| const auto targetPos = lineText.find(target); | ||
| if (targetPos != std::string::npos) { | ||
| location.column = targetPos + 1; | ||
| return location; | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Substring matching may report incorrect column for certain targets.
Using lineText.find(target) can match a substring rather than the exact token. For example, if target = "q" and the line contains qreg q[2], the function returns column 1 (matching the q in qreg) rather than the column of the actual q register reference.
This is a minor concern since the line number will still be correct, and the column is primarily for developer convenience.
🤖 Prompt for AI Agents
In @src/common/parsing/CodePreprocessing.cpp around lines 97 - 103, The current
substring search using lineText.find(target) can match inside other tokens
(e.g., "q" in "qreg"); update the search to find a whole-token match instead:
scan for occurrences of target in lineText and verify character boundaries (or
use a regex with word boundaries like "\\btarget\\b") so the found position
corresponds to a standalone token; change the logic around target, lineText,
targetPos and location.column to use this token-aware check before assigning
location.column and returning.
| if (!isDigits(indexText)) { | ||
| throw ParsingError(formatParseError(code, instructionStart, | ||
| invalidTargetDetail(target, context), | ||
| target)); | ||
| } | ||
| size_t registerIndex = 0; | ||
| try { | ||
| registerIndex = std::stoul(indexText); | ||
| } catch (const std::exception&) { | ||
| throw ParsingError(formatParseError(code, instructionStart, | ||
| invalidTargetDetail(target, context), | ||
| target)); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick | 🔵 Trivial
Defensive error handling is appropriate but consider consolidating the error path.
The isDigits check at line 184 ensures the string contains only digits, so std::stoul at line 191 can only throw std::out_of_range for extremely large values (exceeding unsigned long max). The try-catch is thus defensive coding for edge cases.
Consider combining this into a single validation path for clarity:
Optional: Consolidate validation
if (!isDigits(indexText)) {
throw ParsingError(formatParseError(code, instructionStart,
invalidTargetDetail(target, context),
target));
}
size_t registerIndex = 0;
try {
registerIndex = std::stoul(indexText);
- } catch (const std::exception&) {
+ } catch (const std::out_of_range&) {
+ // Index too large to represent - treat as invalid
throw ParsingError(formatParseError(code, instructionStart,
invalidTargetDetail(target, context),
target));
}The current implementation is functionally correct and safely handles all edge cases.
🤖 Prompt for AI Agents
In @src/common/parsing/CodePreprocessing.cpp around lines 184 - 196, The code
redundantly checks isDigits(indexText) then wraps std::stoul in a broad
try/catch; consolidate by keeping the isDigits check and replacing the try/catch
with a deterministic range check: parse with std::stoull (or use a checked
conversion), compare the resulting unsigned long long against
std::numeric_limits<size_t>::max(), and if it exceeds that limit throw the same
ParsingError (using formatParseError/invalidTargetDetail/target); otherwise
assign the value to registerIndex (static_cast<size_t>) — this removes the broad
exception handling while preserving correctness for out-of-range values.
Description
This pull request improves the debugging highlight functionality and fixes an issue where greyed-out highlights were not correctly reset between executions.
When an issue is detected in the code, the extension highlights the affected line in red and displays a descriptive error message explaining the nature of the problem. This visual feedback helps users quickly identify both the location of the error and its underlying cause.
A second part of the implementation concerns the assertion functionality. After an assertion failure, the extension greys out all lines of code that cannot have caused the error. While this behaviour is intended, the greyed-out highlights were previously not cleared when a new execution started. As a result, lines remained greyed out until the editor was closed and reopened.
Overall, the highlighting functionality provides an efficient way to show where an error occurred and what caused it. This change introduces an explicit reset of all greyed-out highlights on launch and restart events, ensuring that each execution starts with a clean editor state.
Fixes #(issue)
Checklist: