-
Notifications
You must be signed in to change notification settings - Fork 68
Expand file tree
/
Copy pathbeam.ex
More file actions
132 lines (111 loc) · 3.7 KB
/
beam.ex
File metadata and controls
132 lines (111 loc) · 3.7 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
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
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 | bitstring) :: {: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
def debug_info(beam) when is_bitstring(beam) do
do_debug_info(beam)
end
defp do_debug_info(module, path \\ nil)
defp do_debug_info(module, _) when is_atom(module) do
case :code.get_object_code(module) do
{_, beam, beam_path} ->
do_debug_info(beam, beam_path)
:error ->
{:error, "Unknown module"}
end
end
defp do_debug_info(beam, beam_path) do
with {: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}, nil) do
info = Map.put(info, :last_modified, nil)
{:ok, info}
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