378 lines
12 kB
1
defmodule CodeDecompiler do
2
def decompile_to_string(module) when is_atom(module) do
3
path = :code.which(module)
4
5
case :beam_lib.chunks(path, [:abstract_code]) do
6
{:ok, {_, [{:abstract_code, {:raw_abstract_v1, abstract_code}}]}} ->
7
# Convert the abstract format to quoted expressions
8
quoted = Enum.map(abstract_code, &abstract_code_to_quoted/1)
9
|> Enum.reject(&is_nil/1)
10
|> wrap_in_module(module)
11
12
# Format the quoted expression into a string
13
Macro.to_string(quoted)
14
15
{:ok, {_, [{:abstract_code, none}]}} when none in [nil, :none] ->
16
{:error, :no_abstract_code}
17
18
{:error, :beam_lib, {:missing_chunk, _, _}} ->
19
{:error, :no_debug_info}
20
21
{:error, :beam_lib, error} ->
22
{:error, {:beam_lib, error}}
23
24
unexpected ->
25
{:error, {:unexpected_chunk_format, unexpected}}
26
end
27
end
28
29
# Helper to normalize line numbers from either integers or {line, column} tuples
30
defp normalize_line(line) when is_integer(line), do: line
31
defp normalize_line({line, _column}) when is_integer(line), do: line
32
defp normalize_line(_), do: 0
33
34
# Wrap the collected definitions in a module
35
defp wrap_in_module(definitions, module_name) do
36
quote do
37
defmodule unquote(module_name) do
38
unquote_splicing(definitions)
39
end
40
end
41
end
42
43
# Module attributes
44
defp abstract_code_to_quoted({:attribute, _, :module, _}), do: nil # Skip module attribute as we handle it in wrap_in_module
45
defp abstract_code_to_quoted({:attribute, _, :export, _}), do: nil # Skip exports
46
defp abstract_code_to_quoted({:attribute, _, :compile, _}), do: nil # Skip compile attributes
47
defp abstract_code_to_quoted({:attribute, line, name, value}) do
48
quote line: normalize_line(line) do
49
Module.put_attribute(__MODULE__, unquote(name), unquote(convert_attribute_value(value)))
50
end
51
end
52
53
# Functions
54
defp abstract_code_to_quoted({:function, line, name, arity, clauses}) do
55
# Skip module_info functions as they're automatically generated
56
case name do
57
:__info__ -> nil
58
:module_info -> nil
59
name when is_atom(name) ->
60
function_clauses = Enum.map(clauses, &clause_to_quoted/1)
61
62
quote line: normalize_line(line) do
63
def unquote(name)(unquote_splicing(make_vars(arity))) do
64
unquote(function_clauses)
65
end
66
end
67
end
68
end
69
70
# Function clauses
71
defp clause_to_quoted({:clause, line, params, guards, body}) do
72
converted_params = Enum.map(params, &pattern_to_quoted/1)
73
converted_guards = Enum.map(guards, &guard_to_quoted/1)
74
converted_body = Enum.map(body, &expression_to_quoted/1)
75
76
case converted_guards do
77
[] ->
78
quote line: normalize_line(line) do
79
unquote_splicing(converted_params) -> unquote_splicing(converted_body)
80
end
81
guards ->
82
quote line: normalize_line(line) do
83
unquote_splicing(converted_params) when unquote_splicing(guards) -> unquote_splicing(converted_body)
84
end
85
end
86
end
87
88
# Patterns (used in function heads and pattern matching)
89
defp pattern_to_quoted({:match, line, pattern1, pattern2}) do
90
quote line: normalize_line(line) do
91
unquote(pattern_to_quoted(pattern1)) = unquote(pattern_to_quoted(pattern2))
92
end
93
end
94
95
# Add binary pattern support
96
defp pattern_to_quoted({:bin, line, elements}) do
97
quoted_elements = Enum.map(elements, &binary_element_to_quoted/1)
98
quote line: normalize_line(line) do
99
<<unquote_splicing(quoted_elements)>>
100
end
101
end
102
103
defp pattern_to_quoted({:var, line, name}) do
104
quote line: normalize_line(line) do
105
unquote(Macro.var(name, nil))
106
end
107
end
108
109
defp pattern_to_quoted({:integer, line, value}) do
110
quote line: normalize_line(line) do
111
unquote(value)
112
end
113
end
114
115
defp pattern_to_quoted({:atom, line, value}) do
116
quote line: normalize_line(line) do
117
unquote(value)
118
end
119
end
120
121
defp pattern_to_quoted({:cons, line, head, tail}) do
122
quote line: normalize_line(line) do
123
[unquote(pattern_to_quoted(head)) | unquote(pattern_to_quoted(tail))]
124
end
125
end
126
127
defp pattern_to_quoted({:nil, line}) do
128
quote line: normalize_line(line) do
129
[]
130
end
131
end
132
133
defp pattern_to_quoted({:tuple, line, elements}) do
134
quoted_elements = Enum.map(elements, &pattern_to_quoted/1)
135
quote line: normalize_line(line) do
136
{unquote_splicing(quoted_elements)}
137
end
138
end
139
140
defp pattern_to_quoted({:map, line, pairs}) do
141
quoted_pairs = Enum.map(pairs, fn
142
{:map_field_assoc, _, key, value} ->
143
{:%{}, [], [{pattern_to_quoted(key), pattern_to_quoted(value)}]}
144
{:map_field_exact, _, key, value} ->
145
{pattern_to_quoted(key), pattern_to_quoted(value)}
146
{op, k, v} ->
147
{map_op_to_quoted(op), pattern_to_quoted(k), pattern_to_quoted(v)}
148
end)
149
quote line: normalize_line(line) do
150
%{unquote_splicing(quoted_pairs)}
151
end
152
end
153
154
# Guards
155
defp guard_to_quoted(guards) when is_list(guards) do
156
Enum.map(guards, fn guard -> guard_to_quoted_expr(guard) end)
157
end
158
159
defp guard_to_quoted(guard), do: guard_to_quoted_expr(guard)
160
161
# Guard expressions
162
defp guard_to_quoted_expr({:op, line, operator, left, right}) do
163
quote line: normalize_line(line) do
164
unquote({operator, [], [expression_to_quoted(left), expression_to_quoted(right)]})
165
end
166
end
167
168
defp guard_to_quoted_expr({:op, line, operator, operand}) do
169
quote line: normalize_line(line) do
170
unquote({operator, [], [expression_to_quoted(operand)]})
171
end
172
end
173
174
defp guard_to_quoted_expr({:call, line, {:remote, _, {:atom, _, module}, {:atom, _, fun}}, args}) do
175
quoted_args = Enum.map(args, &expression_to_quoted/1)
176
quote line: normalize_line(line) do
177
unquote(module).unquote(fun)(unquote_splicing(quoted_args))
178
end
179
end
180
181
defp guard_to_quoted_expr({:call, line, {:atom, _, fun}, args}) do
182
quoted_args = Enum.map(args, &expression_to_quoted/1)
183
quote line: normalize_line(line) do
184
unquote(fun)(unquote_splicing(quoted_args))
185
end
186
end
187
188
# Add support for variables and other basic terms in guards
189
defp guard_to_quoted_expr(expr), do: expression_to_quoted(expr)
190
191
# Expressions (function bodies)
192
# Binary expressions need to come before general constructs
193
defp expression_to_quoted({:bin, line, elements}) do
194
quoted_elements = Enum.map(elements, &binary_element_to_quoted/1)
195
quote line: normalize_line(line) do
196
<<unquote_splicing(quoted_elements)>>
197
end
198
end
199
200
# Anonymous functions
201
defp expression_to_quoted({:fun, line, {:clauses, clauses}}) do
202
quoted_clauses = Enum.map(clauses, fn {:clause, clause_line, params, guards, body} ->
203
converted_params = Enum.map(params, &pattern_to_quoted/1)
204
converted_guards = Enum.map(guards, &guard_to_quoted/1)
205
converted_body = Enum.map(body, &expression_to_quoted/1)
206
207
case converted_guards do
208
[] ->
209
{:->, [line: normalize_line(clause_line)],
210
[converted_params, {:__block__, [], converted_body}]}
211
guards ->
212
{:->, [line: normalize_line(clause_line)],
213
[[{:when, [], converted_params ++ guards}], {:__block__, [], converted_body}]}
214
end
215
end)
216
217
{:fn, [line: normalize_line(line)], quoted_clauses}
218
end
219
220
defp binary_element_to_quoted({:bin_element, _line, {:string, _sline, value}, :default, :default}) do
221
value
222
end
223
224
defp binary_element_to_quoted({:bin_element, _line, expr, size, type}) do
225
quoted_expr = expression_to_quoted(expr)
226
build_bin_element(quoted_expr, size, type)
227
end
228
229
defp build_bin_element(expr, :default, :default), do: expr
230
defp build_bin_element(expr, size, :default) when is_integer(size), do: quote do: unquote(expr)::size(unquote(size))
231
defp build_bin_element(expr, :default, type), do: quote do: unquote(expr)::unquote(type)
232
defp build_bin_element(expr, size, type), do: quote do: unquote(expr)::size(unquote(size))-unquote(type)
233
234
# List construction
235
defp expression_to_quoted({:cons, line, head, {:nil, _}}) do
236
quote line: normalize_line(line) do
237
[unquote(expression_to_quoted(head))]
238
end
239
end
240
241
defp expression_to_quoted({:cons, line, head, tail}) do
242
quote line: normalize_line(line) do
243
[unquote(expression_to_quoted(head)) | unquote(expression_to_quoted(tail))]
244
end
245
end
246
247
defp expression_to_quoted({:nil, line}) do
248
quote line: normalize_line(line) do
249
[]
250
end
251
end
252
253
# Other expressions
254
defp expression_to_quoted({:match, line, pattern, expr}) do
255
quote line: normalize_line(line) do
256
unquote(pattern_to_quoted(pattern)) = unquote(expression_to_quoted(expr))
257
end
258
end
259
260
defp expression_to_quoted({:call, line, {:remote, _, mod, fun}, args}) do
261
quoted_mod = expression_to_quoted(mod)
262
quoted_fun = expression_to_quoted(fun)
263
quoted_args = Enum.map(args, &expression_to_quoted/1)
264
265
quote line: normalize_line(line) do
266
unquote(quoted_mod).unquote(quoted_fun)(unquote_splicing(quoted_args))
267
end
268
end
269
270
defp expression_to_quoted({:call, line, {:atom, _, fun}, args}) do
271
quoted_args = Enum.map(args, &expression_to_quoted/1)
272
quote line: normalize_line(line) do
273
unquote(fun)(unquote_splicing(quoted_args))
274
end
275
end
276
277
defp expression_to_quoted({:case, line, expr, clauses}) do
278
quoted_expr = expression_to_quoted(expr)
279
quoted_clauses = Enum.map(clauses, &clause_to_quoted/1)
280
281
quote line: normalize_line(line) do
282
case unquote(quoted_expr) do
283
unquote(quoted_clauses)
284
end
285
end
286
end
287
288
defp expression_to_quoted({:block, line, exprs}) do
289
quoted_exprs = Enum.map(exprs, &expression_to_quoted/1)
290
quote line: normalize_line(line) do
291
unquote_splicing(quoted_exprs)
292
end
293
end
294
295
defp expression_to_quoted({:tuple, line, elements}) do
296
quoted_elements = Enum.map(elements, &expression_to_quoted/1)
297
quote line: normalize_line(line) do
298
{unquote_splicing(quoted_elements)}
299
end
300
end
301
302
# Operator expressions
303
defp expression_to_quoted({:op, line, operator, left, right}) do
304
quote line: normalize_line(line) do
305
unquote({operator, [], [expression_to_quoted(left), expression_to_quoted(right)]})
306
end
307
end
308
309
defp expression_to_quoted({:op, line, operator, operand}) do
310
quote line: normalize_line(line) do
311
unquote({operator, [], [expression_to_quoted(operand)]})
312
end
313
end
314
315
# Literals and basic terms
316
defp expression_to_quoted({:atom, line, value}) do
317
quote line: normalize_line(line) do
318
unquote(value)
319
end
320
end
321
322
defp expression_to_quoted({:integer, line, value}) do
323
quote line: normalize_line(line) do
324
unquote(value)
325
end
326
end
327
328
defp expression_to_quoted({:float, line, value}) do
329
quote line: normalize_line(line) do
330
unquote(value)
331
end
332
end
333
334
defp expression_to_quoted({:string, line, value}) do
335
quote line: normalize_line(line) do
336
unquote(value)
337
end
338
end
339
340
defp expression_to_quoted({:var, line, name}) do
341
quote line: normalize_line(line) do
342
unquote(Macro.var(name, nil))
343
end
344
end
345
346
# Maps
347
defp expression_to_quoted({:map, line, []}) do
348
quote line: normalize_line(line) do
349
%{}
350
end
351
end
352
353
defp expression_to_quoted({:map, line, pairs}) do
354
quoted_pairs = Enum.map(pairs, fn
355
{:map_field_assoc, _, key, value} ->
356
{:%{}, [], [{expression_to_quoted(key), expression_to_quoted(value)}]}
357
{op, k, v} ->
358
{map_op_to_quoted(op), expression_to_quoted(k), expression_to_quoted(v)}
359
end)
360
quote line: normalize_line(line) do
361
%{unquote_splicing(quoted_pairs)}
362
end
363
end
364
365
# Helpers
366
defp make_vars(n) when n > 0 do
367
for i <- 1..n//1, do: Macro.var(:"arg#{i}", nil)
368
end
369
defp make_vars(_), do: []
370
371
defp map_op_to_quoted(:exact), do: :%{}
372
defp map_op_to_quoted(:assoc), do: :%{}
373
374
defp convert_attribute_value(value) when is_atom(value) or is_integer(value) or is_float(value) or is_binary(value), do: value
375
defp convert_attribute_value(value) when is_list(value), do: Enum.map(value, &convert_attribute_value/1)
376
defp convert_attribute_value({a, b}), do: {convert_attribute_value(a), convert_attribute_value(b)}
377
defp convert_attribute_value(other), do: other
378
end
379