Skip to content

Commit

Permalink
feat: Function breakpoint support in the Macro tracer
Browse files Browse the repository at this point in the history
  • Loading branch information
slavek-kucera authored Mar 28, 2024
1 parent 6fd038c commit c8f45a6
Show file tree
Hide file tree
Showing 14 changed files with 178 additions and 6 deletions.
1 change: 1 addition & 0 deletions clients/vscode-hlasmplugin/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
- Watch support in the Macro tracer
- Emit MNOTE and PUNCH arguments to the debug console
- Make MNOTE and PUNCH outputs available from VSCode
- Function breakpoint support in the Macro tracer

#### Fixed
- Unknown requests were dropped without a proper response
Expand Down
19 changes: 19 additions & 0 deletions clients/vscode-hlasmplugin/src/test/suite/debugging.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,4 +109,23 @@ suite('Debugging Test Suite', () => {
await helper.debugStop();

}).timeout(20000).slow(10000);

test('Function breakpoint test', async () => {
await helper.addFunctionBreakpoints(['uniQue_maCro']);

await helper.showDocument('function_break');

const session = await helper.debugStartSession();

// Continue until breakpoint is hit
await helper.debugContinue();
const a0 = await session.customRequest('evaluate', { expression: "&A" });
assert.deepStrictEqual(a0, { result: '0', variablesReference: 0 });

await helper.debugContinue();
const a1 = await session.customRequest('evaluate', { expression: "&A" });
assert.deepStrictEqual(a1, { result: '1', variablesReference: 0 });

await helper.debugStop();
}).timeout(20000).slow(10000);
});
4 changes: 4 additions & 0 deletions clients/vscode-hlasmplugin/src/test/suite/testHelper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,10 @@ export async function addBreakpoints(file: string, lines: Array<number>) {
await vscode.debug.addBreakpoints(lines.map(l => new vscode.SourceBreakpoint(new vscode.Location(document.uri, new vscode.Position(l, 0)), true)));
}

export async function addFunctionBreakpoints(functions: Array<string>) {
await vscode.debug.addBreakpoints(functions.map(f => new vscode.FunctionBreakpoint(f)));
}

export async function removeAllBreakpoints() {
await vscode.debug.removeBreakpoints(vscode.debug.breakpoints);
}
Expand Down
8 changes: 8 additions & 0 deletions clients/vscode-hlasmplugin/src/test/workspace/function_break
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
MACRO
UNIQUE_MACRO
MEND

&A SETA 0
UNIQUE_MACRO
&A SETA 1
UNIQUE_MACRO
25 changes: 25 additions & 0 deletions language_server/src/dap/dap_feature.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ void dap_feature::register_methods(std::map<std::string, method>& methods)
add_method("launch", &dap_feature::on_launch, LOG_EVENT);
add_method("setBreakpoints", &dap_feature::on_set_breakpoints, LOG_EVENT);
add_method("setExceptionBreakpoints", &dap_feature::on_set_exception_breakpoints, LOG_EVENT);
add_method("setFunctionBreakpoints", &dap_feature::on_set_function_breakpoints, LOG_EVENT);
add_method("configurationDone", &dap_feature::on_configuration_done);
add_method("threads", &dap_feature::on_threads);
add_method("stackTrace", &dap_feature::on_stack_trace);
Expand Down Expand Up @@ -145,6 +146,7 @@ void dap_feature::on_initialize(const request_id& requested_seq, const nlohmann:
nlohmann::json {
{ "supportsConfigurationDoneRequest", true },
{ "supportsEvaluateForHovers", true },
{ "supportsFunctionBreakpoints", true },
});

line_1_based_ = args.at("linesStartAt1").get<bool>() ? 1 : 0;
Expand Down Expand Up @@ -231,6 +233,29 @@ void dap_feature::on_set_exception_breakpoints(const request_id& request_seq, co
response_->respond(request_seq, "setExceptionBreakpoints", nlohmann::json());
}

void dap_feature::on_set_function_breakpoints(const request_id& request_seq, const nlohmann::json& args)
{
if (!debugger)
return;

nlohmann::json breakpoints_verified = nlohmann::json::array();
std::vector<parser_library::function_breakpoint> breakpoints;

if (auto bpoints_found = args.find("breakpoints"); bpoints_found != args.end())
{
for (auto& bp_json : bpoints_found.value())
{
breakpoints.emplace_back(parser_library::sequence<char>(bp_json.at("name").get<std::string_view>()));
breakpoints_verified.push_back(nlohmann::json { { "verified", true } });
}
}

debugger->function_breakpoints(breakpoints);

response_->respond(
request_seq, "setFunctionBreakpoints", nlohmann::json { { "breakpoints", breakpoints_verified } });
}

void dap_feature::on_configuration_done(const request_id& request_seq, const nlohmann::json&)
{
response_->respond(request_seq, "configurationDone", nlohmann::json());
Expand Down
1 change: 1 addition & 0 deletions language_server/src/dap/dap_feature.h
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ class dap_feature : public feature, public hlasm_plugin::parser_library::debuggi
void on_launch(const request_id& request_seq, const nlohmann::json& args);
void on_set_breakpoints(const request_id& request_seq, const nlohmann::json& args);
void on_set_exception_breakpoints(const request_id& request_seq, const nlohmann::json& args);
void on_set_function_breakpoints(const request_id& request_seq, const nlohmann::json& args);
void on_configuration_done(const request_id& request_seq, const nlohmann::json& args);
void on_threads(const request_id& request_seq, const nlohmann::json& args);
void on_stack_trace(const request_id& request_seq, const nlohmann::json& args);
Expand Down
35 changes: 35 additions & 0 deletions language_server/test/dap/dap_feature_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -351,6 +351,41 @@ TEST_F(feature_launch_test, breakpoint)
feature.on_disconnect(request_id(5), {});
}

const std::string file_function_breakpoint = R"(
LR 1,1
SAM31
)";

TEST_F(feature_launch_test, function_breakpoint)
{
ws_mngr->did_open_file(utils::path::path_to_uri(file_path).c_str(),
0,
file_function_breakpoint.c_str(),
file_function_breakpoint.size());
ws_mngr->idle_handler();

nlohmann::json bp_args { { "breakpoints", R"([{"name":"sam31"}])"_json } };
feature.on_set_function_breakpoints(request_id(47), bp_args);
std::vector<response_mock> expected_resp_bp = {
{ request_id(47), "setFunctionBreakpoints", R"( { "breakpoints":[ {"verified":true} ]})"_json }
};
EXPECT_EQ(resp_provider.responses, expected_resp_bp);
resp_provider.reset();

feature.on_launch(request_id(0), nlohmann::json { { "program", file_path }, { "stopOnEntry", false } });
ws_mngr->idle_handler();
feature.idle_handler(nullptr);
std::vector<response_mock> expected_resp = { { request_id(0), "launch", nlohmann::json() } };
EXPECT_EQ(resp_provider.responses, expected_resp);
wait_for_stopped();
resp_provider.reset();

check_simple_stack_trace(request_id(2), 2);


feature.on_disconnect(request_id(3), {});
}

const std::string file_variables = R"(&VARA SETA 4
&VARB SETB 1
&VARC SETC 'STH'
Expand Down
2 changes: 1 addition & 1 deletion language_server/test/dap/dap_server_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ TEST(dap_server, dap_server)
serv.message_received(initialize_message);

std::vector expected_response_init = {
R"({"body":{"supportsConfigurationDoneRequest":true,"supportsEvaluateForHovers":true},"command":"initialize","request_seq":1,"seq":1,"success":true,"type":"response"})"_json,
R"({"body":{"supportsConfigurationDoneRequest":true,"supportsEvaluateForHovers":true,"supportsFunctionBreakpoints":true},"command":"initialize","request_seq":1,"seq":1,"success":true,"type":"response"})"_json,
R"({"body":null,"event" : "initialized","seq" : 2,"type" : "event"})"_json
};

Expand Down
8 changes: 8 additions & 0 deletions parser_library/include/debugger.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
#ifndef HLASMPLUGIN_PARSERLIBRARY_DEBUGGER_H
#define HLASMPLUGIN_PARSERLIBRARY_DEBUGGER_H

#include <span>

#include "parser_library_export.h"
#include "protocol.h"
#include "range.h"
Expand Down Expand Up @@ -120,6 +122,12 @@ class debugger
[[nodiscard]] breakpoints_t breakpoints(sequence<char> source) const;
[[nodiscard]] breakpoints_t breakpoints(std::string_view source) const { return breakpoints(sequence(source)); }

void function_breakpoints(sequence<function_breakpoint> bps);
void function_breakpoints(std::span<const function_breakpoint> bps)
{
function_breakpoints(sequence<function_breakpoint>(bps));
}

// Retrieval of current context.
stack_frames_t stack_frames() const;
scopes_t scopes(frame_id_t frame_id) const;
Expand Down
10 changes: 9 additions & 1 deletion parser_library/include/protocol.h
Original file line number Diff line number Diff line change
Expand Up @@ -401,12 +401,20 @@ using variables_t = sequence<variable, const debugging::variable_store*>;

struct breakpoint
{
breakpoint(size_t line)
explicit breakpoint(size_t line)
: line(line)
{}
size_t line;
};

struct function_breakpoint
{
explicit function_breakpoint(sequence<char> name)
: name(name)
{}
sequence<char> name;
};

struct output_line
{
int level; // -1 if N/A
Expand Down
33 changes: 30 additions & 3 deletions parser_library/src/debugging/debugger.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,8 @@ class debugger::impl final : public processing::statement_analyzer, output_handl
utils::resource::resource_location_hasher>
breakpoints_;

std::unordered_set<std::string, utils::hashers::string_hasher, std::equal_to<>> function_breakpoints_;

size_t add_variable(std::vector<variable_ptr> vars)
{
variables_[next_var_ref_].variables = std::move(vars);
Expand Down Expand Up @@ -307,10 +309,13 @@ class debugger::impl final : public processing::statement_analyzer, output_handl
if (!resolved_stmt)
return false;

const auto& op_code = resolved_stmt->opcode_ref().value;
// Continue only for non-empty statements
if (resolved_stmt->opcode_ref().value.empty())
if (op_code.empty())
return false;

const bool function_breakpoint_hit = function_breakpoints_.contains(op_code.to_string_view());

range stmt_range = resolved_stmt->stmt_range_ref();

bool breakpoint_hit = false;
Expand All @@ -333,7 +338,8 @@ class debugger::impl final : public processing::statement_analyzer, output_handl
};

// breakpoint check
if (stop_on_next_stmt_ || breakpoint_hit || (stop_on_stack_changes_ && stack_condition_violated(stack_node)))
if (stop_on_next_stmt_ || breakpoint_hit || function_breakpoint_hit
|| (stop_on_stack_changes_ && stack_condition_violated(stack_node)))
{
variables_.clear();
stack_frames_.clear();
Expand All @@ -348,8 +354,17 @@ class debugger::impl final : public processing::statement_analyzer, output_handl
stop_on_stack_condition_ = std::make_pair(stack_node, nullptr);

continue_ = false;

static constexpr const std::string_view reasons[] = {
"entry",
"breakpoint",
"function breakpoint",
"breakpoint",
};
const auto reason_id = breakpoint_hit + 2 * function_breakpoint_hit;

if (event_)
event_->stopped("entry", "");
event_->stopped(reasons[reason_id], "");
}
return !continue_;
}
Expand Down Expand Up @@ -812,6 +827,13 @@ class debugger::impl final : public processing::statement_analyzer, output_handl
return {};
}

void function_breakpoints(std::span<const function_breakpoint> bps)
{
function_breakpoints_.clear();
for (const auto& bp : bps)
function_breakpoints_.emplace(utils::to_upper_copy(std::string_view(bp.name)));
}

~impl() { disconnect(); }
};

Expand Down Expand Up @@ -866,6 +888,11 @@ breakpoints_t debugger::breakpoints(sequence<char> source) const
return result;
}

void debugger::function_breakpoints(sequence<function_breakpoint> bps)
{
pimpl->function_breakpoints(std::span<const function_breakpoint>(bps.begin(), bps.end()));
}

stack_frames_t debugger::stack_frames() const
{
const auto& frames = pimpl->stack_frames();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ using namespace std::chrono_literals;
void debug_event_consumer_s_mock::stopped(
sequence<char> reason, hlasm_plugin::parser_library::sequence<char> addtl_info)
{
(void)reason;
last_reason = std::string_view(reason);
(void)addtl_info;
++stop_count;
stopped_ = true;
Expand Down
3 changes: 3 additions & 0 deletions parser_library/test/debugging/debug_event_consumer_s_mock.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@

class debug_event_consumer_s_mock : public hlasm_plugin::parser_library::debugging::debug_event_consumer
{
std::string last_reason;
bool stopped_ = false;
bool exited_ = false;
size_t stop_count = 0;
Expand Down Expand Up @@ -54,6 +55,8 @@ class debug_event_consumer_s_mock : public hlasm_plugin::parser_library::debuggi

const auto& get_last_mnote() const { return last_mnote; }
const auto& get_last_punch() const { return last_punch; }

const auto& get_last_reason() const { return last_reason; }
};

#endif // !HLASMPLUGIN_PARSERLIBRARY_TEST_DEBUG_EVENT_CONSUMER_S_MOCK_H
33 changes: 33 additions & 0 deletions parser_library/test/debugging/debugger_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1252,6 +1252,39 @@ TEST(debugger, breakpoints_set_get)
EXPECT_EQ(bp.line, bps.begin()->line);
}

TEST(debugger, function_breakpoints)
{
std::string open_code = R"(
LR 1,1
SAM31
)";

file_manager_impl file_manager;
NiceMock<debugger_configuration_provider_mock> dc_provider;
EXPECT_CALL(dc_provider, provide_debugger_configuration).WillRepeatedly(Invoke([&file_manager](auto, auto r) {
r.provide({ .fm = &file_manager });
}));
debugger d;
debug_event_consumer_s_mock m(d);

const resource_location file_loc("test");

file_manager.did_open_file(file_loc, 0, open_code);

function_breakpoint bp(sequence(std::string_view("SAM31")));
d.function_breakpoints(sequence<function_breakpoint>(&bp, 1));

auto [resp, mock] = make_workspace_manager_response(std::in_place_type<workspace_manager_response_mock<bool>>);
EXPECT_CALL(*mock, provide(true));
d.launch(file_loc.get_uri(), dc_provider, false, resp);

m.wait_for_stopped();

EXPECT_EQ(m.get_last_reason(), "function breakpoint");

d.disconnect();
}

TEST(debugger, invalid_file)
{
file_manager_impl file_manager;
Expand Down

0 comments on commit c8f45a6

Please sign in to comment.