Skip to content
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

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 38 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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')
Copy link
Owner

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?

```

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).
Expand All @@ -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]
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

my bad, there's a typo there: decompierl

```

## 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

Expand Down
89 changes: 88 additions & 1 deletion lib/decompilerl.ex
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,
Copy link
Owner

Choose a reason for hiding this comment

The 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 : "),
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would you consider using String.pad_trailing/3 for all the formatting touch ups?

format(exports, "Exported Fns : "),
get_private_functions(functions, exports)
|>format("Private Fns : ")
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💅 nitpick: |> format

]
Enum.join(List.flatten(lines), "\n")
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's no need to flatten, iolists are OK to write

end


defp format([], _prefix) do
[]
end
defp format(behaviours, prefix) do
[h | t] = behaviours
Copy link
Owner

Choose a reason for hiding this comment

The 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
Copy link
Owner

Choose a reason for hiding this comment

The 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)
Copy link
Owner

Choose a reason for hiding this comment

The 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)
Copy link
Owner

Choose a reason for hiding this comment

The 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
Expand All @@ -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) ->
Expand Down
38 changes: 27 additions & 11 deletions lib/decompilerl/cli.ex
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