this repo has no description
at main 12 kB view raw
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