this repo has no description
1defmodule 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))
423end