-
Notifications
You must be signed in to change notification settings - Fork 3
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Now offers a summary of the beam file and not the full thing #2
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -59,6 +59,41 @@ new() -> | |
There we go! `struct` is an Erlang map with a special key `__struct__ => ?MODULE` | ||
that allows pattern matching further down the line. Noice! | ||
|
||
## Summary View | ||
|
||
Sometimes you don't want to fish through all the source code so there is now a summary view. | ||
|
||
This can be called as so: | ||
|
||
```elixir | ||
Decompilerl.summarise('_build/dev/lib/myapp/ebin/Elixir.MyApp.AuthController.beam') | ||
``` | ||
|
||
It returns a summary of the beam file: | ||
``` | ||
File : web/controllers/auth_controller.ex | ||
Module : Elixir.MyApp.AuthController | ||
Behaviours : Elixir.Plug | ||
Exported Fns : __info__/1 | ||
action/2 | ||
call/2 | ||
callback/2 | ||
fake/2 | ||
index/2 | ||
init/1 | ||
Private Fns : action (overridable 2)/2 | ||
authorize_url!/1 | ||
call/2 | ||
callback/2 | ||
fake/2 | ||
get_token!/2 | ||
get_user!/2 | ||
index/2 | ||
init/1 | ||
maybe_create/5 | ||
phoenix_controller_pipeline/2 | ||
``` | ||
|
||
## Command-line interface | ||
|
||
You can build `Decompilerl` as a standalone executable (escript). | ||
|
@@ -73,14 +108,16 @@ $ ./decompilerl | |
|
||
Decompilerl | ||
|
||
usage: decompierl <beam_file> [-o <erl_file> | --output=<erl_file>] | ||
usage: decompierl <beam_file> [-o <erl_file> | --output=<erl_file> | -s | --summary] | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. my bad, there's a typo there: |
||
``` | ||
|
||
## Usage | ||
|
||
By default, `Decompilerl.decompile` spits the Erlang abstract code to stdout. | ||
When provided with a second (optional) argument, it'll dump it to a file. | ||
|
||
adding the option `-s` or `--summary` produces the summary and not the source code | ||
|
||
``` | ||
$ iex -S mix | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,11 +1,98 @@ | ||
defmodule Decompilerl do | ||
|
||
def summarise(module, device \\ :stdio) do | ||
obtain_beam(module) | ||
|> make_summary | ||
|> format_summary | ||
|> write_to(device) | ||
end | ||
|
||
def decompile(module, device \\ :stdio) do | ||
obtain_beam(module) | ||
|> do_decompile | ||
|> write_to(device) | ||
end | ||
|
||
defp format_summary(map) do | ||
%{:file => file, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. IMO it'd be more idiomatic to match (preferably in the function clause) like this: %{file: file, module: module, ...} |
||
:module => module, | ||
:behaviours => behaviours, | ||
:exports => exports, | ||
:functions => functions} = map | ||
lines = [ | ||
"File : " <> file, | ||
"Module : " <> module, | ||
format(behaviours, "Behaviours : "), | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Would you consider using |
||
format(exports, "Exported Fns : "), | ||
get_private_functions(functions, exports) | ||
|>format("Private Fns : ") | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 💅 nitpick: |
||
] | ||
Enum.join(List.flatten(lines), "\n") | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There's no need to |
||
end | ||
|
||
|
||
defp format([], _prefix) do | ||
[] | ||
end | ||
defp format(behaviours, prefix) do | ||
[h | t] = behaviours | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Match in the function clause instead? |
||
lines = for x <- t, do: " " <> x | ||
[prefix <> h] ++ lines | ||
end | ||
|
||
defp get_private_functions(functions, exports) do | ||
Enum.drop_while(functions, fn(x) -> Enum.member?(exports, x) end) | ||
end | ||
|
||
defp make_summary(beam_code) do | ||
{:ok, {_, [abstract_code: {_, ac}]}} = | ||
:beam_lib.chunks(beam_code, [:abstract_code]) | ||
{:tree, _, _, ast} = :erl_syntax.form_list(ac) | ||
map = %{:file => [], | ||
:module => [], | ||
:behaviours => [], | ||
:exports => [], | ||
:functions => []} | ||
Enum.reduce(ast, map, &process_ast/2) | ||
end | ||
|
||
defp process_ast({:attribute, _, :module, module}, map) do | ||
Map.put(map, :module, Atom.to_string(module)) | ||
end | ||
|
||
defp process_ast({:attribute, _, :file, {file, _}}, map) do | ||
Map.put(map, :file, to_string(file)) | ||
end | ||
|
||
defp process_ast({:attribute, _, :export, exports}, map) do | ||
formatted_exports = for {func, arity} <- exports do | ||
make_fn_declaration(func, arity) | ||
end | ||
append_value(map, :exports, formatted_exports) | ||
end | ||
|
||
defp process_ast({:attribute, _, :behaviour, behaviour}, map) do | ||
append_value(map, :behaviours, [Atom.to_string(behaviour)]) | ||
end | ||
|
||
defp process_ast({:function, _, func, arity, _body}, map) do | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Perhaps glue the function clauses together, i.e. no white space in between? |
||
append_value(map, :functions, [make_fn_declaration(func, arity)]) | ||
end | ||
|
||
## dump the rest | ||
defp process_ast(_clause, map) do | ||
map | ||
end | ||
|
||
defp make_fn_declaration(func, arity) do | ||
Atom.to_string(func) <> "/" <> Integer.to_string(arity) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. How about: "#{func}/#{arity}" |
||
end | ||
|
||
defp append_value(map, key, valuelist) when is_list(valuelist) do | ||
%{^key => values} = map | ||
Map.put(map, key, values ++ valuelist) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You could avoid matching and pinning the key with: Map.update(map, key, xs, &(xs ++ &1)) |
||
end | ||
|
||
defp obtain_beam(module) when is_atom(module) do | ||
{^module, beam, _file} = :code.get_object_code(module) | ||
beam | ||
|
@@ -18,13 +105,13 @@ defmodule Decompilerl do | |
defp do_decompile(beam_code) do | ||
{:ok, {_, [abstract_code: {_, ac}]}} = | ||
:beam_lib.chunks(beam_code, [:abstract_code]) | ||
|
||
:erl_prettypr.format(:erl_syntax.form_list(ac)) | ||
end | ||
|
||
defp write_to(code, :stdio) do | ||
IO.puts code | ||
end | ||
|
||
defp write_to(code, file_name) when is_binary(file_name) do | ||
{:ok, result} = | ||
File.open(file_name, [:write], fn(file) -> | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,33 +1,49 @@ | ||
defmodule Decompilerl.CLI do | ||
@switches help: :boolean, output: :string | ||
@aliases h: :help, o: :output | ||
@switches help: :boolean, output: :string, summary: :boolean | ||
@aliases h: :help, o: :output, s: :summary | ||
|
||
def main(argv) do | ||
argv | ||
|> parse_args | ||
|> process | ||
end | ||
|
||
def parse_args(args) do | ||
opts = | ||
defp parse_args(args) do | ||
opts = | ||
OptionParser.parse(args, switches: @switches, aliases: @aliases) | ||
|
||
case opts do | ||
{[], [name], _} -> {name, :stdio} | ||
{[output: output], [name], _} -> {name, output} | ||
_ -> :help | ||
{parsed, args, errors} = opts | ||
case errors do | ||
[] -> | ||
case {Enum.sort(parsed), args} do | ||
{[], [name]} -> | ||
{:decompile, {name, :stdio}} | ||
{[output: output], [name]} -> | ||
{:decompile, {name, output}} | ||
{[output: output, summary: true], [name]} -> | ||
{:summarise, {name, output}} | ||
{[summary: true], [name]} -> | ||
{:summarise, {name, :stdio}} | ||
end | ||
_ -> | ||
:help | ||
end | ||
end | ||
|
||
def process(:help) do | ||
defp process(:help) do | ||
IO.puts """ | ||
Decompilerl | ||
|
||
usage: decompierl <beam_file> [-o <erl_file> | --output=<erl_file>] | ||
usage: decompierl <beam_file> [-o <erl_file> | --output=<erl_file> | -s | --summary] | ||
""" | ||
end | ||
|
||
def process({name, device}) do | ||
defp process({:decompile, {name, device}}) do | ||
Decompilerl.decompile(name, device) | ||
end | ||
|
||
defp process({:summarise, {name, device}}) do | ||
Decompilerl.summarise(name, device) | ||
end | ||
|
||
end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's uncommon for elixir APIs to accept lists as "strings", since strings are binaries by default. What do you think?