Skip to content

Commit 2c65c32

Browse files
authored
Do not crash debugger on on supported command (#294)
* do not crash on unsupported command in debugger * remove not needed handle_info the process is not trapping exits * add a few matches on return values * do not default to nil process name * no need to start int as it's started by the debugger app * add comment * remove assertions that can never be met since the changes in 25e21ff * reenable debugger tests * add test
1 parent f4f8580 commit 2c65c32

File tree

4 files changed

+78
-22
lines changed

4 files changed

+78
-22
lines changed

apps/elixir_ls_debugger/lib/debugger/output.ex

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,18 @@ defmodule ElixirLS.Debugger.Output do
1313

1414
## Client API
1515

16-
def start(name \\ nil) do
16+
def start(name \\ __MODULE__) do
1717
GenServer.start(__MODULE__, :ok, name: name)
1818
end
1919

2020
def send_response(server \\ __MODULE__, request_packet, response_body) do
2121
GenServer.call(server, {:send_response, request_packet, response_body})
2222
end
2323

24+
def send_error_response(server \\ __MODULE__, request_packet, message, format, variables) do
25+
GenServer.call(server, {:send_error_response, request_packet, message, format, variables})
26+
end
27+
2428
def send_event(server \\ __MODULE__, event, body) do
2529
GenServer.call(server, {:send_event, event, body})
2630
end
@@ -46,7 +50,21 @@ defmodule ElixirLS.Debugger.Output do
4650
{:reply, :ok, seq + 1}
4751
end
4852

49-
@impl GenServer
53+
def handle_call({:send_error_response, request_packet, message, format, variables}, _from, seq) do
54+
send(
55+
error_response(
56+
seq,
57+
request_packet["seq"],
58+
request_packet["command"],
59+
message,
60+
format,
61+
variables
62+
)
63+
)
64+
65+
{:reply, :ok, seq + 1}
66+
end
67+
5068
def handle_call({:send_event, event, body}, _from, seq) do
5169
send(event(seq, event, body))
5270
{:reply, :ok, seq + 1}

apps/elixir_ls_debugger/lib/debugger/protocol.basic.ex

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,26 @@ defmodule ElixirLS.Debugger.Protocol.Basic do
3636
end
3737
end
3838

39+
defmacro error_response(seq, request_seq, command, message, format, variables) do
40+
quote do
41+
%{
42+
"type" => "response",
43+
"command" => unquote(command),
44+
"seq" => unquote(seq),
45+
"request_seq" => unquote(request_seq),
46+
"success" => false,
47+
"message" => unquote(message),
48+
"body" => %{
49+
"error" => %{
50+
"id" => unquote(seq),
51+
"format" => unquote(format),
52+
"variables" => unquote(variables)
53+
}
54+
}
55+
}
56+
end
57+
end
58+
3959
defmacro event(seq, event, body) do
4060
quote do
4161
%{

apps/elixir_ls_debugger/lib/debugger/server.ex

Lines changed: 28 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,10 @@ defmodule ElixirLS.Debugger.Server do
1010
increment it any time we assign an ID.
1111
"""
1212

13+
defmodule ServerError do
14+
defexception [:message, :format, :variables]
15+
end
16+
1317
alias ElixirLS.Debugger.{Output, Stacktrace, Protocol, Variables}
1418
use GenServer
1519
use Protocol
@@ -54,16 +58,21 @@ defmodule ElixirLS.Debugger.Server do
5458

5559
@impl GenServer
5660
def init(opts) do
57-
{:ok, _pid} = :int.start()
5861
state = if opts[:output], do: %__MODULE__{output: opts[:output]}, else: %__MODULE__{}
5962
{:ok, state}
6063
end
6164

6265
@impl GenServer
6366
def handle_cast({:receive_packet, request(_, _) = packet}, state) do
64-
{response_body, state} = handle_request(packet, state)
65-
Output.send_response(packet, response_body)
66-
{:noreply, state}
67+
try do
68+
{response_body, state} = handle_request(packet, state)
69+
Output.send_response(packet, response_body)
70+
{:noreply, state}
71+
rescue
72+
e in ServerError ->
73+
Output.send_error_response(packet, e.message, e.format, e.variables)
74+
{:noreply, state}
75+
end
6776
end
6877

6978
@impl GenServer
@@ -79,6 +88,7 @@ defmodule ElixirLS.Debugger.Server do
7988
{:noreply, state}
8089
end
8190

91+
# the `:DOWN` message is not delivered under normal conditions as the process calls `Process.sleep(:infinity)`
8292
@impl GenServer
8393
def handle_info({:DOWN, ref, :process, _pid, reason}, %{task_ref: ref} = state) do
8494
exit_code =
@@ -101,11 +111,6 @@ defmodule ElixirLS.Debugger.Server do
101111
{:noreply, %{state | task_ref: nil}}
102112
end
103113

104-
@impl GenServer
105-
def handle_info({:EXIT, _, :normal}, state) do
106-
{:noreply, state}
107-
end
108-
109114
# If we get the disconnect request from the client, we send :disconnect to the server so it will
110115
# die right after responding to the request
111116
@impl GenServer
@@ -293,7 +298,7 @@ defmodule ElixirLS.Debugger.Server do
293298
pid = state.threads[thread_id]
294299
state = remove_paused_process(state, pid)
295300

296-
:int.continue(pid)
301+
:ok = :int.continue(pid)
297302
{%{"allThreadsContinued" => false}, state}
298303
end
299304

@@ -321,6 +326,15 @@ defmodule ElixirLS.Debugger.Server do
321326
{%{}, state}
322327
end
323328

329+
defp handle_request(request(_, command), _state) when is_binary(command) do
330+
raise ServerError,
331+
message: "notSupported",
332+
format: "Debugger request {command} is currently not supported",
333+
variables: %{
334+
"command" => command
335+
}
336+
end
337+
324338
defp remove_paused_process(state, pid) do
325339
update_in(state.paused_processes, fn paused_processes ->
326340
Map.delete(paused_processes, pid)
@@ -530,7 +544,7 @@ defmodule ElixirLS.Debugger.Server do
530544
|> Enum.filter(&interpretable?(&1, exclude_modules))
531545
|> Enum.map(fn mod ->
532546
try do
533-
:int.ni(mod)
547+
{:module, _} = :int.ni(mod)
534548
catch
535549
_, _ ->
536550
IO.warn(
@@ -609,8 +623,8 @@ defmodule ElixirLS.Debugger.Server do
609623

610624
defp save_and_reload(module, beam_bin) do
611625
:ok = File.write(Path.join(@temp_beam_dir, to_string(module) <> ".beam"), beam_bin)
612-
:code.delete(module)
613-
:int.ni(module)
626+
true = :code.delete(module)
627+
{:module, _} = :int.ni(module)
614628
end
615629

616630
defp set_breakpoints(path, lines) do
@@ -640,7 +654,7 @@ defmodule ElixirLS.Debugger.Server do
640654
defp set_breakpoint(module, line) do
641655
case :int.ni(module) do
642656
{:module, _} ->
643-
:int.break(module, line)
657+
:ok = :int.break(module, line)
644658
{:ok, module, line}
645659

646660
_ ->

apps/elixir_ls_debugger/test/debugger_test.exs

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,6 @@ defmodule ElixirLS.Debugger.ServerTest do
2626
{:ok, %{server: server}}
2727
end
2828

29-
# Causes test suite not to finish
30-
@tag :pending
3129
test "basic debugging", %{server: server} do
3230
in_fixture(__DIR__, "mix_project", fn ->
3331
Server.receive_packet(server, initialize_req(1, %{}))
@@ -128,13 +126,19 @@ defmodule ElixirLS.Debugger.ServerTest do
128126
Server.receive_packet(server, continue_req(10, thread_id))
129127
assert_receive response(_, 10, "continue", %{"allThreadsContinued" => false})
130128

131-
assert_receive(event(_, "exited", %{"exitCode" => 0}))
132-
assert_receive(event(_, "terminated", %{"restart" => false}))
129+
Server.receive_packet(server, request(11, "someRequest", %{"threadId" => 123}))
130+
131+
assert_receive error_response(
132+
_,
133+
11,
134+
"someRequest",
135+
"notSupported",
136+
"Debugger request {command} is currently not supported",
137+
%{"command" => "someRequest"}
138+
)
133139
end)
134140
end
135141

136-
# Causes test suite not to finish
137-
@tag :pending
138142
test "sets breakpoints in erlang modules", %{server: server} do
139143
in_fixture(__DIR__, "mix_project", fn ->
140144
Server.receive_packet(server, initialize_req(1, %{}))

0 commit comments

Comments
 (0)