Current section
Files
Jump to
Current section
Files
lib/iris/calls.ex
defmodule Iris.Calls do
@moduledoc """
This module houses functions that create Call structs and in generating the incoming and outgoing
calls of each function.
"""
alias Iris.Entity.Module.{Method, Method.Call}
import Iris.Utils
def generate_call(%Method{} = caller, all_methods) do
generate_call({caller.module, caller.name, caller.arity}, all_methods)
end
def generate_call(_call = {call_m, call_f, call_a}, all_methods) do
select =
all_methods
|> Enum.filter(fn m ->
m.module == call_m && m.name == call_f && m.arity == call_a
end)
case select do
# checks whether the method is part of current project
# if empty then it must be a built-in-function
# or method imported from a dependency
[] ->
method = %Method{module: call_m, name: call_f, arity: call_a}
method =
case call_m do
"erlang" -> %{method | html_type_text: "BIF"}
_ -> %{method | html_type_text: "IMP"}
end
%Call{clickable: false, method: method}
# method is part of current project
# no need to change html_type_text.
[method] ->
%Call{clickable: true, method: method}
end
end
def assign_in_out_calls(
%Iris.Entity.Module{} = module,
all_methods,
all_out_calls,
all_in_calls
) do
module_methods = Enum.filter(all_methods, fn method -> method.module == module.module end)
module_out_calls =
Enum.reduce(module_methods, %{}, fn method, acc ->
Map.put(acc, method, Map.get(all_out_calls, method, []))
end)
module_in_calls =
Enum.reduce(module_methods, %{}, fn method, acc ->
Map.put(acc, method, Map.get(all_in_calls, method, []))
end)
%{module | out_calls: module_out_calls, in_calls: module_in_calls}
end
@doc ~S"""
All call instructions that can be extracted from compiled code
returns a list of instructions
format { type, arity, function }
function -> {module, function, arity}
"""
def get_call_instructions(code) do
code
|> Enum.map(fn instruction ->
ret =
case instruction do
{:call, a, f} -> {:call, a, f}
{:call_last, a, f, _n} -> {:call_last, a, f}
{:call_ext_last, a, f, _n} -> {:call_ext_last, a, f}
{:call_only, a, f} -> {:call_only, a, f}
{:call_ext_only, a, f} -> {:call_ext_only, a, f}
# methods that are referenced using the capture operator notation &method_name/arity
# show up in :make_fun3 instruction along with autogenerated/inlned methods
{:make_fun3, f, _, _, _, _} -> {:make_fun3, f}
_ -> nil
end
ret
end)
|> Enum.filter(fn val -> val != nil end)
end
@doc ~S"""
Returns the instruction in {m,f,a} format
see [get_call_instructions/1] return type to understand instruction format
"""
def normalize_call_instructions(%Method{} = method) do
normalized_instr =
method.call_instructions
|> Enum.map(fn instr ->
case instr do
{_call_inst, _arity, {:extfunc, m, f, a}} ->
{m, f, a}
{_call_inst, _arity, {m, f, a}} ->
{m, f, a}
{:make_fun3, {m, f, a}} ->
{m, f, a}
inst ->
IO.inspect(
{"FATAL : EXITING : [normalize_call_instructions/1] not implemented for instruction type ",
inst}
)
System.halt(1)
end
end)
|> Enum.map(fn {m, f, a} -> {Atom.to_string(m), Atom.to_string(f), a} end)
# make_fun3 generates inlined methods as well so filter them out
|> Enum.filter(fn {_m, f, _a} -> is_name_not_autogenerated?(f) end)
|> Enum.map(fn {m, f, a} ->
cond do
String.starts_with?(m, "Elixir.") ->
m = m |> String.split("Elixir.") |> Enum.at(1)
{m, f, a}
true ->
{m, f, a}
end
end)
|> Enum.map(fn {m, f, a} ->
# using Integer.to_string to avoid inequality during comparison while generating calls
# todo: fix this. standardize arity as string/integer in the entire code.
{f, a} = extract_name_from_auto_generated(f, Integer.to_string(a))
{m, f, a}
end)
%{method | call_instructions: normalized_instr}
end
@doc ~S"""
Divides instructions into recursive and non-recursive
if more than one recursive instruction is found method is flagged as recursive and only non_recursive instructions are returned
else return all instructions
"""
def filter_recursive_calls(%Method{} = method) do
{recursive_calls, non_recursive_calls} =
method.call_instructions
|> Enum.reduce({[], []}, fn {m, f, a} = call, {rec, non_rec} ->
cond do
m == method.module && f == method.name && a == method.arity -> {[call | rec], non_rec}
true -> {rec, [call | non_rec]}
end
end)
case recursive_calls do
[] -> method
_ -> %{method | call_instructions: non_recursive_calls, is_recursive: true}
end
end
@doc ~S"""
Remove duplicate instructions
"""
def filter_duplicate_calls(%Method{} = method) do
%{method | call_instructions: Enum.uniq(method.call_instructions)}
end
end