hrzndhrn / beam_file

BeamFile - A peek into the BEAM file
MIT License
79 stars 6 forks source link

Invalid output from elixir_quoted/1 in Elixir 1.17.1 #17

Open bartblast opened 1 week ago

bartblast commented 1 week ago

Hey Marcus, after upgrading to Elixir 1.17.1 I started noticing invalid output from elixir_quoted/1.

Here's the result of BeamFile.elixir_quoted!(Collectable.HashDict) call in Elixir 1.17.1:

{:defmodule, [context: Elixir, import: Kernel],
 [
   {:__aliases__, [alias: false], [Collectable.HashDict]},
   [
     do: {:__block__, [],
      [
        {:def, [line: 286, context: Protocol],
         [{:__impl__, [], [:for]}, [do: HashDict]]},
        {:def, [line: 286, context: Protocol],
         [{:__impl__, [], [:protocol]}, [do: Collectable]]},
        {:def, [line: 287, column: 7],
         [
           {:into, [], [{:original, [version: 0, line: 287, column: 12], nil}]},
           [
             do: {:__block__, [line: 286],
              [
                {:=, [line: 289, column: 12],
                 [
                   {:module, [version: 1, line: 289, column: 5], nil},
                   {{:., [line: 289, column: 20], [:erlang, :binary_to_atom]},
                    [line: 289, column: 21],
                    ["HashDict", {:__block__, [], [:utf8]}]}
                 ]},
                {:=, [line: 291, column: 19],
                 [
                   {:collector_fun, [version: 6, line: 291, column: 5], nil},
                   {:fn, [line: 291, column: 21],
                    [
                      {:->, [line: 292, column: 35],
                       [
                         [
                           {:dict, [version: 2, line: 292, column: 7], nil},
                           {:cont,
                            {{:key, [version: 3, line: 292, column: 22], nil},
                             {:value, [version: 4, line: 292, column: 27], nil}}}
                         ],
                         {{:., [line: 292, column: 44],
                           [
                             {:module, [version: 1, line: 292, column: 38], nil},
                             :put
                           ]}, [line: 292, column: 45],
                          [
                            {:dict, [version: 2, line: 292, column: 49], nil},
                            {:key, [version: 3, line: 292, column: 55], nil},
                            {:value, [version: 4, line: 292, column: 60], nil}
                          ]}
                       ]},
                      {:->, [line: 293, column: 19],
                       [
                         [
                           {:dict, [version: 5, line: 293, column: 7], nil},
                           :done
                         ],
                         {:dict, [version: 5, line: 293, column: 22], nil}
                       ]},
                      {:->, [line: 294, column: 16],
                       [[{:_, [line: 294, column: 7], nil}, :halt], :ok]}
                    ]}
                 ]},
                {{:original, [version: 0, line: 297, column: 6], nil},
                 {:collector_fun, [version: 6, line: 297, column: 16], nil}}
              ]}
           ]
         ]}
      ]}
   ]
 ]}

Notice how the args of :erlang.binary_to_atom/2 call are encoded: ["HashDict", {:__block__, [], [:utf8]}], I suppose this should be ["HashDict", :utf8].

Here's the result of BeamFile.debug_info!(Collectable.HashDict) call:

%{
  attributes: [
    behaviour: Collectable,
    __impl__: [protocol: Collectable, for: HashDict]
  ],
  line: 286,
  module: Collectable.HashDict,
  file: "/home/runner/work/elixir/elixir/lib/elixir/lib/hash_dict.ex",
  deprecated: [],
  unreachable: [],
  struct: nil,
  after_verify: [],
  defines_behaviour: false,
  definitions: [
    {{:into, 1}, :def, [line: 287, column: 7],
     [
       {[line: 287, column: 7],
        [{:original, [version: 0, line: 287, column: 12], nil}], [],
        {:__block__, [line: 286],
         [
           {:=, [line: 289, column: 12],
            [
              {:module, [version: 1, line: 289, column: 5], nil},
              {{:., [line: 289, column: 20], [:erlang, :binary_to_atom]},
               [line: 289, column: 21], ["HashDict", :utf8]}
            ]},
           {:=, [line: 291, column: 19],
            [
              {:collector_fun, [version: 6, line: 291, column: 5], nil},
              {:fn, [line: 291, column: 21],
               [
                 {:->, [line: 292, column: 35],
                  [
                    [
                      {:dict, [version: 2, line: 292, column: 7], nil},
                      {:cont,
                       {{:key, [version: 3, line: 292, column: 22], nil},
                        {:value, [version: 4, line: 292, column: 27], nil}}}
                    ],
                    {{:., [line: 292, column: 44],
                      [
                        {:module, [version: 1, line: 292, column: 38], nil},
                        :put
                      ]}, [line: 292, column: 45],
                     [
                       {:dict, [version: 2, line: 292, column: 49], nil},
                       {:key, [version: 3, line: 292, column: 55], nil},
                       {:value, [version: 4, line: 292, column: 60], nil}
                     ]}
                  ]},
                 {:->, [line: 293, column: 19],
                  [
                    [{:dict, [version: 5, line: 293, column: 7], nil}, :done],
                    {:dict, [version: 5, line: 293, column: 22], nil}
                  ]},
                 {:->, [line: 294, column: 16],
                  [[{:_, [line: 294, column: 7], nil}, :halt], :ok]}
               ]}
            ]},
           {{:original, [version: 0, line: 297, column: 6], nil},
            {:collector_fun, [version: 6, line: 297, column: 16], nil}}
         ]}}
     ]},
    {{:__impl__, 1}, :def, [line: 286, context: Protocol],
     [
       {[line: 286, context: Protocol], [:for], [], HashDict},
       {[line: 286, context: Protocol], [:protocol], [], Collectable}
     ]}
  ],
  uses_behaviours: [Collectable],
  impls: [],
  compile_opts: [],
  relative_file: "lib/hash_dict.ex"
}

And here's the original Elixir code for the corresponding protocol implementation:

defimpl Collectable, for: HashDict do
  def into(original) do
    # Avoid warnings about HashDict being deprecated.
    module = String.to_atom("HashDict")

    collector_fun = fn
      dict, {:cont, {key, value}} -> module.put(dict, key, value)
      dict, :done -> dict
      _, :halt -> :ok
    end

    {original, collector_fun}
  end
end

(https://github.com/elixir-lang/elixir/blob/0793fc91b992ba1e3acbe5a1286a5259bb97bcf0/lib/elixir/lib/hash_dict.ex#L286C1-L299C4)

bartblast commented 1 week ago

PS: the last working Elixir version that I tested on is 1.16.3-otp-26 (1.17.0 doesn't work either).

NickNeck commented 1 week ago

Hello Bart, thanks for reporting. I will start next week to test and fix beam_file to work with Elixir 1.17. The package always needs some updates for any new Elixir version.

bartblast commented 1 week ago

Got ya, thanks a lot!

NickNeck commented 2 days ago

Hello @bartblast , I have released version 0.6.1. Can you please check if that fixes this issue.

The problem was that the BeamFile.Normalizer expanded :utf8 to {:__block__, [], [:utf8]}. That is needed for Elixir 1.13 and when the normalised AST goes to the Elixir formatter, without the change the formatter throws an error (I don't know why). In the released version this "normalisation" step is restricted to Elixir 1.13 and the function elixir_code.