Skip to content

Instantly share code, notes, and snippets.

@Ajwah
Last active May 6, 2020 18:37
Show Gist options
  • Save Ajwah/4dee98bbb1e0c9257bb52dcd6c6802e2 to your computer and use it in GitHub Desktop.
Save Ajwah/4dee98bbb1e0c9257bb52dcd6c6802e2 to your computer and use it in GitHub Desktop.
Debugging Breakpoint VSCode Elixir
```elixir
%ElixirSense.Core.Metadata{calls: %{
7 => [%ElixirSense.Core.State.CallInfo{arity: 0, func: :atom, mod: nil, position: {7, 11}}, %ElixirSense.Core.State.CallInfo{arity: 1, func: :a, mod: nil, position: {7, 9}}, %ElixirSense.Core.State.CallInfo{arity: 1, func: :spec, mod: nil, position: {7, 4}}],
10 => [%ElixirSense.Core.State.CallInfo{arity: 0, func: :hello, mod: nil, position: {10, 9}}, %ElixirSense.Core.State.CallInfo{arity: 1, func: :spec, mod: nil, position: {10, 4}}]}, error: nil,
lines_to_env: %{
1 => %ElixirSense.Core.State.Env{aliases: [], attributes: [], behaviours: [], imports: [], module: Dummy, module_variants: [Dummy], protocol: nil, protocol_variants: [], requires: [], scope: :Dummy, scope_id: 1, vars: []},
7 => %ElixirSense.Core.State.Env{aliases: [], attributes: [], behaviours: [], imports: [], module: Dummy, module_variants: [Dummy], protocol: nil, protocol_variants: [], requires: [], scope: :Dummy, scope_id: 2, vars: []},
8 => %ElixirSense.Core.State.Env{aliases: [], attributes: [], behaviours: [], imports: [], module: Dummy, module_variants: [Dummy], protocol: nil, protocol_variants: [], requires: [], scope: {:a, 1}, scope_id: 2, vars: []},
10 => %ElixirSense.Core.State.Env{aliases: [], attributes: [], behaviours: [], imports: [], module: Dummy, module_variants: [Dummy], protocol: nil, protocol_variants: [], requires: [], scope: :Dummy, scope_id: 2, vars: []},
11 => %ElixirSense.Core.State.Env{aliases: [], attributes: [], behaviours: [], imports: [], module: Dummy, module_variants: [Dummy], protocol: nil, protocol_variants: [], requires: [], scope: {:hello, 0}, scope_id: 2, vars: []},
19 => %ElixirSense.Core.State.Env{aliases: [], attributes: [], behaviours: [], imports: [], module: Dummy, module_variants: [Dummy], protocol: nil, protocol_variants: [], requires: [], scope: {:hello2, 0}, scope_id: 2, vars: []}}, mods_funs_to_positions: %{{Dummy, :__info__, 1
} => %ElixirSense.Core.State.ModFunInfo{params: [[{:atom, [line: 1, column: 11], nil}]], positions: [{1, 11}], target: nil, type: :def}, {Dummy, :__info__, nil
} => %ElixirSense.Core.State.ModFunInfo{params: [[{:atom, [line: 1, column: 11], nil}]], positions: [{1, 11}], target: nil, type: :def}, {Dummy, :a, 1
} => %ElixirSense.Core.State.ModFunInfo{params: [[:a]], positions: [{8, 7}], target: nil, type: :def}, {Dummy, :a, nil
} => %ElixirSense.Core.State.ModFunInfo{params: [[:a]], positions: [{8, 7}], target: nil, type: :def}, {Dummy, :hello, 0
} => %ElixirSense.Core.State.ModFunInfo{params: [[]], positions: [{11, 7}], target: nil, type: :def}, {Dummy, :hello, nil
} => %ElixirSense.Core.State.ModFunInfo{params: [[]], positions: [{11, 7}], target: nil, type: :def}, {Dummy, :hello2, 0
} => %ElixirSense.Core.State.ModFunInfo{params: [[]], positions: [{19, 7}], target: nil, type: :def}, {Dummy, :hello2, nil
} => %ElixirSense.Core.State.ModFunInfo{params: [[]], positions: [{19, 7}], target: nil, type: :def}, {Dummy, :module_info, 0
} => %ElixirSense.Core.State.ModFunInfo{params: [[]], positions: [{1, 11}], target: nil, type: :def}, {Dummy, :module_info, 1
} => %ElixirSense.Core.State.ModFunInfo{params: [[{:atom, [line: 1, column: 11], nil}]], positions: [{1, 11}], target: nil, type: :def}, {Dummy, :module_info, nil
} => %ElixirSense.Core.State.ModFunInfo{params: [[{:atom, [line: 1, column: 11], nil}], []], positions: [{1, 11}, {1, 11}], target: nil, type: :def}, {Dummy, nil, nil
} => %ElixirSense.Core.State.ModFunInfo{params: [nil], positions: [{1, 11}], target: nil, type: :defmodule}}, source: "defmodule Dummy do\n # defmodule B do\n # @spec a(number) :: number\n # def a(x), do: x + 1\n # end\n\n @spec a(atom) :: :ok\n def a(:a), do: :ok\n\n @spec hello :: :world\n def hello do\n a = :world\n b = 1\n c = 2\n\n a\n end\n\n def hello2 do\n a = :world2\n b = 2\n c = 22\n\n a\n end\nend\n", specs: %{{Dummy, :a, 1
} => %ElixirSense.Core.State.SpecInfo{args: [["atom"]], kind: :spec, name: :a, positions: [{7, 3}], specs: ["@spec a(atom) :: :ok"]}, {Dummy, :a, nil
} => %ElixirSense.Core.State.SpecInfo{args: [["atom"]], kind: :spec, name: :a, positions: [{7, 3}], specs: ["@spec a(atom) :: :ok"]}, {Dummy, :hello, 0
} => %ElixirSense.Core.State.SpecInfo{args: [[]], kind: :spec, name: :hello, positions: [{10, 3}], specs: ["@spec hello :: :world"]}, {Dummy, :hello, nil
} => %ElixirSense.Core.State.SpecInfo{args: [[]], kind: :spec, name: :hello, positions: [{10, 3}], specs: ["@spec hello :: :world"]}}, structs: %{}, types: %{}, vars_info_per_scope_id: %{
1 => %{},
2 => %{},
3 => %{},
4 => %{a: %ElixirSense.Core.State.VarInfo{is_definition: false, name: :a, positions: [{12, 5}, {16, 5}], scope_id: 4}, b: %ElixirSense.Core.State.VarInfo{is_definition: false, name: :b, positions: [{13, 5}], scope_id: 4}, c: %ElixirSense.Core.State.VarInfo{is_definition: false, name: :c, positions: [{14, 5}], scope_id: 4}},
5 => %{a: %ElixirSense.Core.State.VarInfo{is_definition: false, name: :a, positions: [{20, 5}, {24, 5}], scope_id: 5}, b: %ElixirSense.Core.State.VarInfo{is_definition: false, name: :b, positions: [{21, 5}], scope_id: 5}, c: %ElixirSense.Core.State.VarInfo{is_definition: false, name: :c, positions: [{22, 5}], scope_id: 5}}}}
```
@Ajwah
Copy link
Author

Ajwah commented May 6, 2020

Here is a dummy repo in which I set a breakpoint in the following location
I annotated Debugger.Server as follows:

  defp set_breakpoints(path, lines) do
    IO.puts("set_breakpoints: #{inspect(path)}")
    if Path.extname(path) == ".erl" do
      module = String.to_atom(Path.basename(path, ".erl"))
      for line <- lines, do: set_breakpoint(module, line)
    else
      try do
        IO.puts("[set_breakpoints]. Attempting to parse file: #{path}")
        metadata = ElixirSense.Core.Parser.parse_file(path, false, false, nil)
        IO.puts("[set_breakpoints]. Parsing succeeded. metadata: #{inspect(metadata)}. Now looping over all lines")
        for line <- lines do
          IO.puts("[set_breakpoints]. Handling line: #{inspect(line)}")
          env = ElixirSense.Core.Metadata.get_env(metadata, line)
          IO.inspect("[set_breakpoints]. env: #{inspect(env)}")
          if env.module == nil do
            {:error, "Could not determine module at line"}
          else
            set_breakpoint(env.module, line)
          end
        end
      rescue
        error ->
          IO.inspect("[set_breakpoints]. Failure: #{inspect(error)}")
          for _line <- lines, do: {:error, Exception.format_exit(error)}
      end
    end
  end

Above in gistfiletext1 you can see the metadata that gets returned when executing this line:
metadata = ElixirSense.Core.Parser.parse_file(path, false, false, nil)
When it comes to handling line 15, this line:
ElixirSense.Core.Metadata.get_env(metadata, line) tries to get line 15 from the metadata above under the subkey: lines_to_env as can be seen here

The problem here is that only the starting lines of a function are present; which in our case is line 11. When it is not able to find an entry for line 15, the standard env is returned where env.module is nil thus we get: {:error, "Could not determine module at line"}

I also tried to setting a breakpoint right at line 11 to no avail.
I get the following output in the debug console:

[set_breakpoints]. Handling line: 11
"[set_breakpoints]. env: %ElixirSense.Core.State.Env{aliases: [], attributes: [], behaviours: [], imports: [], module: Dummy, module_variants: [Dummy], protocol: nil, protocol_variants: [], requires: [], scope: {:hello, 0}, scope_id: 2, vars: []}"
set_breakpoint: Dummy | 11
"Succeeded setting breakpoint"

which thus means that it has executed: :int.break(module, line) in the function below:

  defp set_breakpoint(module, line) do
    IO.puts("set_breakpoint: #{inspect(module)} | #{inspect(line)}")
    case :int.ni(module) do
      {:module, _} ->
        :int.break(module, line)
        IO.inspect("Succeeded setting breakpoint")
        {:ok, module, line}

      _ ->
        {:error, "Cannot interpret module #{inspect(module)}"}
    end
  end

but despite that, when I execute in terminal: iex -S mix followed by Dummy.hello, vscode does not intercept the breakpoint.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment