Skip to content

Commit 585345a

Browse files
committed
improve config validation
improve error messages in common launch error cases do not emit telemetry for common launch errors
1 parent e808295 commit 585345a

File tree

3 files changed

+257
-43
lines changed

3 files changed

+257
-43
lines changed

apps/elixir_ls_debugger/lib/debugger/server.ex

Lines changed: 146 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -750,11 +750,12 @@ defmodule ElixirLS.Debugger.Server do
750750
:normal ->
751751
:ok
752752

753-
:shutdown ->
754-
:ok
753+
{%ServerError{} = error, stack} ->
754+
exit_code = 1
755+
Output.send_event("exited", %{"exitCode" => exit_code})
756+
Output.send_event("terminated", %{"restart" => false})
755757

756-
{:shutdown, _} ->
757-
:ok
758+
reraise error, stack
758759

759760
_other ->
760761
message = "Launch request failed with reason\n" <> Exception.format_exit(reason)
@@ -769,6 +770,7 @@ defmodule ElixirLS.Debugger.Server do
769770
message: "launchError",
770771
format: message,
771772
variables: %{},
773+
send_telemetry: false,
772774
show_user: true
773775
end
774776
end
@@ -1630,6 +1632,16 @@ defmodule ElixirLS.Debugger.Server do
16301632

16311633
project_dir =
16321634
if project_dir not in [nil, ""] do
1635+
if not is_binary(project_dir) do
1636+
raise ServerError,
1637+
message: "argumentError",
1638+
format:
1639+
"invalid `projectDir` in launch config. Expected string or nil, got #{inspect(project_dir)}",
1640+
variables: %{},
1641+
send_telemetry: false,
1642+
show_user: true
1643+
end
1644+
16331645
Output.debugger_console("Starting debugger in directory: #{project_dir}\n")
16341646
project_dir
16351647
else
@@ -1643,12 +1655,44 @@ defmodule ElixirLS.Debugger.Server do
16431655
end
16441656

16451657
task = config["task"]
1658+
1659+
if not (is_nil(task) or is_binary(task)) do
1660+
raise ServerError,
1661+
message: "argumentError",
1662+
format:
1663+
"invalid `taskArgs` in launch config. Expected string or nil, got #{inspect(task)}",
1664+
variables: %{},
1665+
send_telemetry: false,
1666+
show_user: true
1667+
end
1668+
16461669
task_args = config["taskArgs"] || []
1670+
1671+
if not (is_list(task_args) and Enum.all?(task_args, &is_binary/1)) do
1672+
raise ServerError,
1673+
message: "argumentError",
1674+
format:
1675+
"invalid `taskArgs` in launch config. Expected list of strings or nil, got #{inspect(task_args)}",
1676+
variables: %{},
1677+
send_telemetry: false,
1678+
show_user: true
1679+
end
1680+
16471681
auto_interpret_files? = Map.get(config, "debugAutoInterpretAllModules", true)
16481682

16491683
set_env_vars(config["env"])
16501684

1651-
File.cd!(project_dir)
1685+
try do
1686+
File.cd!(project_dir)
1687+
rescue
1688+
e in File.Error ->
1689+
raise ServerError,
1690+
message: "argumentError",
1691+
format: Exception.format_banner(:error, e, __STACKTRACE__),
1692+
variables: %{},
1693+
send_telemetry: false,
1694+
show_user: true
1695+
end
16521696

16531697
# the startup sequence here is taken from
16541698
# https://github.com/elixir-lang/elixir/blob/v1.14.4/lib/mix/lib/mix/cli.ex#L158
@@ -1683,10 +1727,22 @@ defmodule ElixirLS.Debugger.Server do
16831727
# make sure ANSI is disabled
16841728
Application.put_env(:elixir, :ansi_enabled, false)
16851729

1686-
unless is_list(task_args) and "--no-compile" in task_args do
1687-
case Mix.Task.run("compile", ["--ignore-module-conflict"]) do
1688-
{:error, reason} ->
1689-
raise reason
1730+
unless "--no-compile" in task_args do
1731+
case Mix.Task.run("compile", ["--ignore-module-conflict", "--return-errors"]) do
1732+
{:error, diagnostics} ->
1733+
message =
1734+
diagnostics
1735+
|> Enum.filter(fn %Mix.Task.Compiler.Diagnostic{} = diag ->
1736+
diag.severity == :error
1737+
end)
1738+
|> Enum.map_join("\n", fn %Mix.Task.Compiler.Diagnostic{} = diag -> diag.message end)
1739+
1740+
raise ServerError,
1741+
message: "launchError",
1742+
format: message,
1743+
variables: %{},
1744+
send_telemetry: false,
1745+
show_user: true
16901746

16911747
_ ->
16921748
:ok
@@ -1703,6 +1759,16 @@ defmodule ElixirLS.Debugger.Server do
17031759
config
17041760
|> Map.get("excludeModules", [])
17051761

1762+
if not (is_list(exclude_module_names) and Enum.all?(exclude_module_names, &is_binary/1)) do
1763+
raise ServerError,
1764+
message: "argumentError",
1765+
format:
1766+
"invalid `excludeModules` in launch config. Expected list of strings or nil, got #{inspect(exclude_module_names)}",
1767+
variables: %{},
1768+
send_telemetry: false,
1769+
show_user: true
1770+
end
1771+
17061772
exclude_module_pattern =
17071773
exclude_module_names
17081774
|> Enum.map(&wildcard_module_name_to_pattern/1)
@@ -1714,17 +1780,57 @@ defmodule ElixirLS.Debugger.Server do
17141780
auto_interpret_modules(Mix.Project.build_path(), exclude_module_pattern)
17151781
end
17161782

1717-
if required_files = config["requireFiles"], do: require_files(required_files)
1783+
required_files = Map.get(config, "requireFiles", [])
17181784

1719-
if interpret_modules_patterns = config["debugInterpretModulesPatterns"] do
1720-
interpret_specified_modules(interpret_modules_patterns, exclude_module_pattern)
1785+
if not (is_list(required_files) and Enum.all?(required_files, &is_binary/1)) do
1786+
raise ServerError,
1787+
message: "argumentError",
1788+
format:
1789+
"invalid `requireFiles` in launch config. Expected list of strings or nil, got #{inspect(required_files)}",
1790+
variables: %{},
1791+
send_telemetry: false,
1792+
show_user: true
17211793
end
1794+
1795+
require_files(required_files)
1796+
1797+
interpret_modules_patterns = Map.get(config, "debugInterpretModulesPatterns", [])
1798+
1799+
if not (is_list(interpret_modules_patterns) and
1800+
Enum.all?(interpret_modules_patterns, &is_binary/1)) do
1801+
raise ServerError,
1802+
message: "argumentError",
1803+
format:
1804+
"invalid `debugInterpretModulesPatterns` in launch config. Expected list of strings or nil, got #{inspect(interpret_modules_patterns)}",
1805+
variables: %{},
1806+
send_telemetry: false,
1807+
show_user: true
1808+
end
1809+
1810+
interpret_specified_modules(interpret_modules_patterns, exclude_module_pattern)
17221811
else
17231812
Output.debugger_console("Running without debugging")
17241813
end
17251814

17261815
updated_config = Map.merge(config, %{"task" => task, "taskArgs" => task_args})
17271816
send(server, {:ok, updated_config})
1817+
rescue
1818+
e in [
1819+
Mix.Error,
1820+
Mix.NoProjectError,
1821+
Mix.ElixirVersionError,
1822+
Mix.InvalidTaskError,
1823+
Mix.NoTaskError,
1824+
CompileError,
1825+
SyntaxError,
1826+
TokenMissingError
1827+
] ->
1828+
raise ServerError,
1829+
message: "launchError",
1830+
format: Exception.format_banner(:error, e, __STACKTRACE__),
1831+
variables: %{},
1832+
send_telemetry: false,
1833+
show_user: true
17281834
end
17291835

17301836
defp set_env_vars(env) when is_map(env) do
@@ -1736,27 +1842,43 @@ defmodule ElixirLS.Debugger.Server do
17361842
"Cannot set environment variables to #{inspect(env)}: #{Exception.message(e)}"
17371843
)
17381844

1739-
Output.debugger_important(
1740-
"Invalid `env` in launch configuration. Expected a map with string key value pairs, got #{inspect(env)}."
1741-
)
1845+
raise ServerError,
1846+
message: "argumentError",
1847+
format:
1848+
"invalid `env` in launch configuration. Expected a map with string key value pairs, got #{inspect(env)}",
1849+
variables: %{},
1850+
send_telemetry: false,
1851+
show_user: true
17421852
end
17431853

17441854
:ok
17451855
end
17461856

17471857
defp set_env_vars(env) when is_nil(env), do: :ok
17481858

1859+
defp set_env_vars(env) do
1860+
raise ServerError,
1861+
message: "argumentError",
1862+
format:
1863+
"invalid `env` in launch configuration. Expected a map with string key value pairs, got #{inspect(env)}",
1864+
variables: %{},
1865+
send_telemetry: false,
1866+
show_user: true
1867+
end
1868+
17491869
defp set_stack_trace_mode("all"), do: :int.stack_trace(:all)
17501870
defp set_stack_trace_mode("no_tail"), do: :int.stack_trace(:no_tail)
17511871
defp set_stack_trace_mode("false"), do: :int.stack_trace(false)
17521872
defp set_stack_trace_mode(false), do: :int.stack_trace(false)
17531873
defp set_stack_trace_mode(nil), do: nil
17541874

1755-
defp set_stack_trace_mode(_) do
1875+
defp set_stack_trace_mode(mode) do
17561876
raise ServerError,
17571877
message: "argumentError",
1758-
format: "stackTraceMode must be `all`, `no_tail`, or `false`",
1878+
format:
1879+
"invalid `stackTraceMode` in launch configuration. Must be `all`, `no_tail` or `false`, got #{inspect(mode)}",
17591880
variables: %{},
1881+
send_telemetry: false,
17601882
show_user: true
17611883
end
17621884

@@ -1861,9 +1983,11 @@ defmodule ElixirLS.Debugger.Server do
18611983
# files to load via the launch configuration. They must be in the correct order (for example,
18621984
# test helpers before tests). We save the .beam files to a temporary folder which we add to the
18631985
# code path.
1986+
defp require_files([]), do: :ok
1987+
18641988
defp require_files(required_files) do
1865-
{:ok, _} = File.rm_rf(@temp_beam_dir)
1866-
:ok = File.mkdir_p(@temp_beam_dir)
1989+
File.rm_rf!(@temp_beam_dir)
1990+
File.mkdir_p!(@temp_beam_dir)
18671991
true = Code.append_path(Path.expand(@temp_beam_dir))
18681992

18691993
for path <- required_files,
@@ -1874,6 +1998,8 @@ defmodule ElixirLS.Debugger.Server do
18741998
do: save_and_reload(module, beam_bin)
18751999
end
18762000

2001+
defp interpret_specified_modules([], _exclude_module_pattern), do: :ok
2002+
18772003
defp interpret_specified_modules(file_patterns, exclude_module_pattern) do
18782004
regexes =
18792005
Enum.map(file_patterns, fn pattern ->
@@ -1886,6 +2012,7 @@ defmodule ElixirLS.Debugger.Server do
18862012
message: "argumentError",
18872013
format: "Unable to compile file pattern {pattern} into a regex: {error}",
18882014
variables: %{"pattern" => inspect(pattern), "error" => inspect(error)},
2015+
send_telemetry: false,
18892016
show_user: true
18902017
end
18912018
end)

apps/elixir_ls_debugger/test/debugger_test.exs

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -482,7 +482,7 @@ defmodule ElixirLS.Debugger.ServerTest do
482482
2,
483483
"launch",
484484
"launchError",
485-
"Launch request failed with reason" <> _,
485+
"** (Mix.NoTaskError) The task \"ru/n\" could not be found" <> _,
486486
%{},
487487
_,
488488
_
@@ -492,14 +492,6 @@ defmodule ElixirLS.Debugger.ServerTest do
492492

493493
refute_receive(event(_, "initialized", %{}))
494494

495-
assert_receive event(_, "output", %{
496-
"category" => "console",
497-
"output" =>
498-
"Launch request failed with reason\nan exception was raised:\n ** (Mix.NoTaskError)" <>
499-
_
500-
}),
501-
3000
502-
503495
assert_receive(
504496
event(_, "exited", %{
505497
"exitCode" => 1

0 commit comments

Comments
 (0)