Skip to content
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

Make debugger conform to DAP 1.51 continue/stepin/stepout/stepover #678

Merged
merged 29 commits into from
Feb 19, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
13a16e7
remove not supported SetExceptionBreakpoints request handler
lukaszsamson Jan 8, 2022
290826e
implement function breakpoints
lukaszsamson Jan 8, 2022
5ba0b4c
test erlang breakpoints
lukaszsamson Jan 9, 2022
563ed28
fix small memory leak when unsetting last breakpoint in file
lukaszsamson Jan 9, 2022
b54a312
add more breakpoint tests
lukaszsamson Jan 9, 2022
0a6b1cb
make tests more synchronous
lukaszsamson Jan 9, 2022
179f407
add function breakpoints tests
lukaszsamson Jan 9, 2022
e2d1980
interpret modules
lukaszsamson Jan 9, 2022
be3e1e4
extract and test mfa parsing
lukaszsamson Jan 9, 2022
250edec
run formatter
lukaszsamson Jan 9, 2022
af2375d
Update readme
lukaszsamson Jan 9, 2022
9abf6a4
cleanup
lukaszsamson Jan 15, 2022
e1a4622
extract and test erlang binding processing
lukaszsamson Jan 9, 2022
40db642
breakpoint conditions support added
lukaszsamson Jan 15, 2022
48b26ae
update readme
lukaszsamson Jan 15, 2022
2325e9f
log when expression crashes
lukaszsamson Jan 15, 2022
2c68c45
add support for conditional function breakpoints
lukaszsamson Jan 15, 2022
152263c
update readme
lukaszsamson Jan 15, 2022
63db83c
add support for breakpoint hit condition
lukaszsamson Jan 16, 2022
9a671b9
readme updated
lukaszsamson Jan 16, 2022
8bca134
implement log message
lukaszsamson Jan 16, 2022
41ac672
readme updated
lukaszsamson Jan 16, 2022
4ef2380
add support for terminateThreads request
lukaszsamson Jan 22, 2022
3f339fe
add support for pause
lukaszsamson Jan 22, 2022
6eca209
make continue, next, stepIn, stepout requests conform to DAP 1.51
lukaszsamson Feb 8, 2022
69471e3
cleanup :int workaround
lukaszsamson Feb 10, 2022
b4a09df
tests wip
lukaszsamson Feb 10, 2022
5fee01c
Merge branch 'master' of github.com:elixir-lsp/elixir-ls into ls-cont…
lukaszsamson Feb 19, 2022
2104b1b
format
lukaszsamson Feb 19, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
120 changes: 62 additions & 58 deletions apps/elixir_ls_debugger/lib/debugger/server.ex
Original file line number Diff line number Diff line change
Expand Up @@ -186,11 +186,12 @@ defmodule ElixirLS.Debugger.Server do
)

{thread_id, threads_inverse} = state.threads_inverse |> Map.pop(pid)
state = remove_paused_process(state, pid)
paused_processes = remove_paused_process(state, pid)

state = %{
state
| threads: state.threads |> Map.delete(thread_id),
paused_processes: paused_processes,
threads_inverse: threads_inverse
}

Expand Down Expand Up @@ -565,76 +566,48 @@ defmodule ElixirLS.Debugger.Server do
{json, state}
end

defp handle_request(continue_req(_, thread_id), state = %__MODULE__{}) do
defp handle_request(continue_req(_, thread_id) = args, state = %__MODULE__{}) do
pid = get_pid_by_thread_id!(state, thread_id)

try do
:int.continue(pid)
state = remove_paused_process(state, pid)
{%{"allThreadsContinued" => false}, state}
rescue
e in MatchError ->
raise ServerError,
message: "serverError",
format: ":int.continue failed: {message}",
variables: %{
"message" => inspect(Exception.message(e))
}
end
safe_int_action(pid, :continue)

paused_processes = remove_paused_process(state, pid)
paused_processes = maybe_continue_other_processes(args, paused_processes, pid)

processes_paused? = paused_processes |> Map.keys() |> Enum.any?(&is_pid/1)

{%{"allThreadsContinued" => not processes_paused?},
%{state | paused_processes: paused_processes}}
end

defp handle_request(next_req(_, thread_id), state = %__MODULE__{}) do
defp handle_request(next_req(_, thread_id) = args, state = %__MODULE__{}) do
pid = get_pid_by_thread_id!(state, thread_id)

try do
:int.next(pid)
state = remove_paused_process(state, pid)
{%{}, state}
rescue
e in MatchError ->
raise ServerError,
message: "serverError",
format: ":int.next failed: {message}",
variables: %{
"message" => inspect(Exception.message(e))
}
end
safe_int_action(pid, :next)
paused_processes = remove_paused_process(state, pid)

{%{},
%{state | paused_processes: maybe_continue_other_processes(args, paused_processes, pid)}}
end

defp handle_request(step_in_req(_, thread_id), state = %__MODULE__{}) do
defp handle_request(step_in_req(_, thread_id) = args, state = %__MODULE__{}) do
pid = get_pid_by_thread_id!(state, thread_id)

try do
:int.step(pid)
state = remove_paused_process(state, pid)
{%{}, state}
rescue
e in MatchError ->
raise ServerError,
message: "serverError",
format: ":int.stop failed: {message}",
variables: %{
"message" => inspect(Exception.message(e))
}
end
safe_int_action(pid, :step)
paused_processes = remove_paused_process(state, pid)

{%{},
%{state | paused_processes: maybe_continue_other_processes(args, paused_processes, pid)}}
end

defp handle_request(step_out_req(_, thread_id), state = %__MODULE__{}) do
defp handle_request(step_out_req(_, thread_id) = args, state = %__MODULE__{}) do
pid = get_pid_by_thread_id!(state, thread_id)

try do
:int.finish(pid)
state = remove_paused_process(state, pid)
{%{}, state}
rescue
e in MatchError ->
raise ServerError,
message: "serverError",
format: ":int.finish failed: {message}",
variables: %{
"message" => inspect(Exception.message(e))
}
end
safe_int_action(pid, :finish)
paused_processes = remove_paused_process(state, pid)

{%{},
%{state | paused_processes: maybe_continue_other_processes(args, paused_processes, pid)}}
end

defp handle_request(request(_, command), _state = %__MODULE__{}) when is_binary(command) do
Expand All @@ -646,6 +619,36 @@ defmodule ElixirLS.Debugger.Server do
}
end

defp maybe_continue_other_processes(%{"singleThread" => true}, paused_processes, requested_pid) do
resumed_pids =
for {paused_pid, %PausedProcess{ref: ref}} when paused_pid != requested_pid <-
paused_processes do
safe_int_action(paused_pid, :continue)
true = Process.demonitor(ref, [:flush])
paused_pid
end

paused_processes |> Map.drop(resumed_pids)
end

defp maybe_continue_other_processes(_, paused_processes, _requested_pid), do: paused_processes

# TODO consider removing this workaround as the problem seems to no longer affect OTP 24
defp safe_int_action(pid, action) do
apply(:int, action, [pid])
:ok
catch
kind, payload ->
# when stepping out of interpreted code a MatchError is risen inside :int module (at least in OTP 23)
IO.warn(":int.#{action}(#{inspect(pid)}) failed: #{Exception.format(kind, payload)}")

unless action == :continue do
safe_int_action(pid, :continue)
end

:ok
end

defp get_pid_by_thread_id!(state = %__MODULE__{}, thread_id) do
case state.threads[thread_id] do
nil ->
Expand All @@ -668,7 +671,7 @@ defmodule ElixirLS.Debugger.Server do
true = Process.demonitor(process.ref, [:flush])
end

%__MODULE__{state | paused_processes: paused_processes}
paused_processes
end

defp variables(state = %__MODULE__{}, pid, var, start, count, filter) do
Expand Down Expand Up @@ -948,6 +951,7 @@ defmodule ElixirLS.Debugger.Server do
"supportsValueFormattingOptions" => false,
"supportsExceptionInfoRequest" => false,
"supportsTerminateThreadsRequest" => true,
"supportsSingleThreadExecutionRequests" => true,
"supportTerminateDebuggee" => false
}
end
Expand Down
4 changes: 2 additions & 2 deletions apps/elixir_ls_debugger/test/debugger_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,7 @@ defmodule ElixirLS.Debugger.ServerTest do
1000

Server.receive_packet(server, continue_req(10, thread_id))
assert_receive response(_, 10, "continue", %{"allThreadsContinued" => false})
assert_receive response(_, 10, "continue", %{"allThreadsContinued" => true})
end)
end

Expand Down Expand Up @@ -333,7 +333,7 @@ defmodule ElixirLS.Debugger.ServerTest do
)

Server.receive_packet(server, continue_req(15, thread_id))
assert_receive response(_, 15, "continue", %{"allThreadsContinued" => false})
assert_receive response(_, 15, "continue", %{"allThreadsContinued" => true})

Server.receive_packet(server, stacktrace_req(7, thread_id))
thread_id_str = inspect(thread_id)
Expand Down