-
Notifications
You must be signed in to change notification settings - Fork 68
Expand file tree
/
Copy pathbeam.ex
More file actions
113 lines (96 loc) · 3.3 KB
/
beam.ex
File metadata and controls
113 lines (96 loc) · 3.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
defmodule ElixirScript.Beam do
@moduledoc false
@doc """
Takes a module and finds the expanded AST
from the debug info inside the beam file.
For protocols, this will return a list of
all the protocol implementations
"""
@spec debug_info(atom) :: {:ok | :error, map | binary}
def debug_info(module)
# We get debug info from String and then replace
# functions in it with equivalents in ElixirScript.String.
# This is so that we don't include the unicode database
# in our output
def debug_info(String) do
{:ok, info} = do_debug_info(String)
{:ok, ex_string_info} = do_debug_info(ElixirScript.String)
definitions = replace_definitions(info.definitions, ex_string_info.definitions)
info = %{info | definitions: definitions}
{:ok, info}
end
# Replace some modules with ElixirScript versions
def debug_info(module) when module in [Agent] do
case do_debug_info(Module.concat(ElixirScript, module)) do
{:ok, info} ->
{:ok, Map.put(info, :module, module)}
e ->
e
end
end
def debug_info(module) when is_atom(module) do
do_debug_info(module)
end
defp do_debug_info(module) when is_atom(module) do
with {_, beam, beam_path} <- :code.get_object_code(module),
{:ok, {^module, [debug_info: {:debug_info_v1, backend, data}]}} <- :beam_lib.chunks(beam, [:debug_info]),
{:ok, {^module, attribute_info}} = :beam_lib.chunks(beam, [:attributes]) do
if Keyword.get(attribute_info[:attributes], :protocol) do
get_protocol_implementations(module)
else
backend.debug_info(:elixir_v1, module, data, [])
|> process_debug_info(beam_path)
end
else
:error ->
{:error, "Unknown module"}
{:error, :beam_lib, {:unknown_chunk, "non_existing.beam", :debug_info}} ->
{:error, "Unsupported version of Erlang"}
{:error, :beam_lib, {:missing_chunk, _ , _}} ->
{:error, "Debug info not available"}
{:error, :beam_lib, {:file_error, "non_existing.beam", :enoent}} ->
{:error, "Debug info not available"}
end
end
defp process_debug_info({:ok, info}, beam_path) do
info = case File.stat(beam_path, time: :posix) do
{:ok, file_info} ->
Map.put(info, :last_modified, file_info.mtime)
_ ->
Map.put(info, :last_modified, nil)
end
{:ok, info}
end
defp process_debug_info(error, _) do
error
end
defp get_protocol_implementations(module) do
implementations = module
|> Protocol.extract_impls(:code.get_path())
|> Enum.map(fn(x) -> Module.concat([module, x]) end)
|> Enum.map(fn(x) ->
case debug_info(x) do
{:ok, info} ->
{x, info}
_ ->
raise "Unable to compile protocol implementation #{inspect x}"
end
end)
{:ok, module, implementations}
end
defp replace_definitions(original_definitions, replacement_definitions) do
Enum.map(original_definitions, fn
{{function, arity}, type, _, _} = ast ->
ex_ast = Enum.find(replacement_definitions, fn
{{ex_function, ex_arity}, ex_type, _, _} ->
ex_function == function and ex_arity == arity and ex_type == type
end)
case ex_ast do
nil ->
ast
_ ->
ex_ast
end
end)
end
end