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