%%
%% %CopyrightBegin%
%% 
%% Copyright Ericsson AB 1998-2017. All Rights Reserved.
%% 
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
%% You may obtain a copy of the License at
%%
%%     http://www.apache.org/licenses/LICENSE-2.0
%%
%% Unless required by applicable law or agreed to in writing, software
%% distributed under the License is distributed on an "AS IS" BASIS,
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%% See the License for the specific language governing permissions and
%% limitations under the License.
%% 
%% %CopyrightEnd%
%%
-module(int).

%% External exports
-export([i/1, i/2, ni/1, ni/2, n/1, nn/1, interpreted/0, file/1,
	 interpretable/1]).
-export([auto_attach/0, auto_attach/1, auto_attach/2,
	 stack_trace/0, stack_trace/1]).
-export([break/2, delete_break/2, break_in/3, del_break_in/3,
	 no_break/0, no_break/1,
	 disable_break/2, enable_break/2,
	 action_at_break/3, test_at_break/3, get_binding/2,
	 all_breaks/0, all_breaks/1]).
-export([snapshot/0, clear/0]).
-export([continue/1, continue/3]).

%% External exports only to be used by Debugger
-export([start/0, stop/0, subscribe/0]).
-export([attach/2, step/1, next/1, finish/1]).

%% External exports only to be used by an attached process
-export([attached/1, meta/2, meta/3, contents/2, functions/1]).

%% External export only to be used by error_handler
-export([eval/3]).

-include_lib("kernel/include/file.hrl").

%%==Erlang Interpreter================================================
%%
%% int
%% ---
%% Interface module.
%%
%% i
%% -
%% Interface module to int, retained for backwards compatibility only.
%%
%% dbg_debugged
%% ------------
%% Contains the message loops for a debugged process and is the main
%% entry point from the breakpoint handler in the error_handler module
%% (via the int module).
%%
%% When a process is debugged, most code is executed in another
%% process, called the meta process. When the meta process is
%% interpreting code, the process being debugged just waits in a
%% receive loop in dbg_debugged. However the debugged process itself
%% calls any BIFs that must execute in the correct process (such as
%% link/1 and spawn_link/1), and external code which is not
%% interpreted.
%%
%% dbg_icmd, dbg_ieval
%% -------------------
%% Code for the meta process.
%%
%% dbg_iserver
%% -----------
%% Interpreter main process, keeping and distributing information
%% about interpreted modules and debugged processes.
%%
%% dbg_idb
%% -------
%% ETS wrapper, allowing transparent access to tables at a remote node.
%%
%% dbg_iload
%% ---------
%% Code for interpreting a module.
%%====================================================================
 
%%====================================================================
%% External exports
%%====================================================================

%%--------------------------------------------------------------------
%% i(AbsMods) -> {module,Mod} | error | ok
%% ni(AbsMods) -> {module,Mod} | error | ok
%%   AbsMods = AbsMod | [AbsMod]
%%     AbsMod = atom() | string()
%%     Mod = atom()
%%     Options = term() ignored
%%--------------------------------------------------------------------
i(AbsMods) -> i2(AbsMods, local, ok).
i(AbsMods, _Options) -> i2(AbsMods, local, ok).
ni(AbsMods) -> i2(AbsMods, distributed, ok).
ni(AbsMods, _Options) -> i2(AbsMods, distributed, ok).
    
i2([AbsMod|AbsMods], Dist, Acc)
  when is_atom(AbsMod); is_list(AbsMod); is_tuple(AbsMod) -> 
    Res = int_mod(AbsMod, Dist),
    case Acc of
	error ->
	    i2(AbsMods, Dist, Acc);
	_ ->
	    i2(AbsMods, Dist, Res)
    end;
i2([], _Dist, Acc) ->
    Acc;
i2(AbsMod, Dist, _Acc) when is_atom(AbsMod); is_list(AbsMod); is_tuple(AbsMod) ->
    int_mod(AbsMod, Dist).

%%--------------------------------------------------------------------
%% n(AbsMods) -> ok
%% nn(AbsMods) -> ok
%%--------------------------------------------------------------------
n(AbsMods) -> n2(AbsMods, local).
nn(AbsMods) -> n2(AbsMods, distributed).

n2([AbsMod|AbsMods], Dist) when is_atom(AbsMod); is_list(AbsMod) ->
    del_mod(AbsMod, Dist),
    n2(AbsMods, Dist);
n2([AbsMod], Dist) when is_atom(AbsMod); is_list(AbsMod) ->
    del_mod(AbsMod, Dist);
n2([], _Dist) ->
    ok;
n2(AbsMod, Dist) when is_atom(AbsMod); is_list(AbsMod) ->
    del_mod(AbsMod, Dist).

%%--------------------------------------------------------------------
%% interpreted() -> [Mod]
%%--------------------------------------------------------------------
interpreted() ->
    dbg_iserver:safe_call(all_interpreted).

%%--------------------------------------------------------------------
%% file(Mod) -> File | {error, not_loaded}
%%   Mod = atom()
%%   File = string()
%%--------------------------------------------------------------------
file(Mod) when is_atom(Mod) ->
    dbg_iserver:safe_call({file, Mod}).

%%--------------------------------------------------------------------
%% interpretable(AbsMod) -> true | {error, Reason}
%%   AbsMod = Mod | File
%%   Reason = no_src | no_beam | no_debug_info | badarg | {app, App}
%%--------------------------------------------------------------------
interpretable(AbsMod) ->
    case check(AbsMod) of
	{ok, _Res} -> true;
	Error -> Error
    end.

%%--------------------------------------------------------------------
%% auto_attach() -> false | {Flags, Function}
%% auto_attach(false)
%% auto_attach(false|Flags, Function)
%%   Flags = Flag | [Flag]
%%     Flag = init | break | exit
%%   Function = {Mod, Func} | {Mod, Func, Args}
%% Will result in calling:
%%  spawn(Mod, Func, [Dist, Pid, Meta | Args]) (living process) or
%%  spawn(Mod, Func, [Dist, Pid, Reason, Info | Args]) (dead process)
%%--------------------------------------------------------------------
auto_attach() ->
    dbg_iserver:safe_call(get_auto_attach).

auto_attach(false) ->
    dbg_iserver:safe_cast({set_auto_attach, false}).

auto_attach([], _Function) ->
    auto_attach(false);
auto_attach(Flags, {Mod, Func}) ->
    auto_attach(Flags, {Mod, Func, []});
auto_attach(Flags, {Mod, Func, Args}) when is_atom(Mod),is_atom(Func),is_list(Args) ->
    check_flags(Flags),
    dbg_iserver:safe_cast({set_auto_attach, Flags, {Mod, Func, Args}}).

check_flags([init|Flags]) -> check_flags(Flags);
check_flags([break|Flags]) -> check_flags(Flags);
check_flags([exit|Flags]) -> check_flags(Flags);
check_flags([]) -> true.

%%--------------------------------------------------------------------
%% stack_trace() -> Flag
%% stack_trace(Flag)
%%   Flag = all | true | no_tail | false
%%--------------------------------------------------------------------
stack_trace() ->
    dbg_iserver:safe_call(get_stack_trace).

stack_trace(true) ->
    stack_trace(all);
stack_trace(Flag) ->
    check_flag(Flag),
    dbg_iserver:safe_cast({set_stack_trace, Flag}).

check_flag(all) -> true;
check_flag(no_tail) -> true;
check_flag(false) -> true.

%%--------------------------------------------------------------------
%% break(Mod, Line) -> ok | {error, break_exists}
%% delete_break(Mod, Line) -> ok
%% break_in(Mod, Func, Arity) -> ok | {error, function_not_found}
%% del_break_in(Mod, Function, Arity) -> ok | {error, function_not_found}
%% no_break()
%% no_break(Mod)
%% disable_break(Mod, Line) -> ok
%% enable_break(Mod, Line) -> ok
%% action_at_break(Mod, Line, Action) -> ok
%% test_at_break(Mod, Line, Function) -> ok
%% get_binding(Var, Bindings) -> {value, Value} | unbound
%% all_breaks() -> [Break]
%% all_breaks(Mod) -> [Break]
%%   Mod = atom()
%%   Line = integer()
%%   Func = atom() function name
%%   Arity = integer()
%%   Action = enable | disable | delete
%%   Function = {Mod, Func} must have arity 1 (Bindings)
%%   Var = atom()
%%   Bindings = Value = term()
%%   Break = {Point, Options}
%%     Point = {Mod, Line}
%%     Options = [Status, Action, null, Cond]
%%       Status = active | inactive
%%       Cond = null | Function
%%--------------------------------------------------------------------
break(Mod, Line) when is_atom(Mod), is_integer(Line) ->
    dbg_iserver:safe_call({new_break, {Mod, Line},
			   [active, enable, null, null]}).

delete_break(Mod, Line) when is_atom(Mod), is_integer(Line) ->
    dbg_iserver:safe_cast({delete_break, {Mod, Line}}).

break_in(Mod, Func, Arity) when is_atom(Mod), is_atom(Func), is_integer(Arity) ->
    case dbg_iserver:safe_call({is_interpreted, Mod, Func, Arity}) of
	{true, Clauses} ->
	    Lines = first_lines(Clauses),
	    lists:foreach(fun(Line) -> break(Mod, Line) end, Lines);
	false ->
	    {error, function_not_found}
    end.

del_break_in(Mod, Func, Arity) when is_atom(Mod), is_atom(Func), is_integer(Arity) ->
    case dbg_iserver:safe_call({is_interpreted, Mod, Func, Arity}) of
	{true, Clauses} ->
	    Lines = first_lines(Clauses),
	    lists:foreach(fun(Line) -> delete_break(Mod, Line) end,
			  Lines);
	false ->
	    {error, function_not_found}
    end.

first_lines(Clauses) ->
    [first_line(Clause) || Clause <- Clauses].

first_line({clause,_L,_Vars,_,Exprs}) ->
    first_line(Exprs);
first_line([Expr|_Exprs]) -> % Expr = {Op, Line, ..varying no of args..}
    element(2, Expr).

no_break() ->
    dbg_iserver:safe_cast(no_break).

no_break(Mod) when is_atom(Mod) ->
    dbg_iserver:safe_cast({no_break, Mod}).

disable_break(Mod, Line) when is_atom(Mod), is_integer(Line) ->
    dbg_iserver:safe_cast({break_option, {Mod, Line}, status, inactive}).
    
enable_break(Mod, Line) when is_atom(Mod), is_integer(Line) ->
    dbg_iserver:safe_cast({break_option, {Mod, Line}, status, active}).

action_at_break(Mod, Line, Action) when is_atom(Mod), is_integer(Line) ->
    check_action(Action),
    dbg_iserver:safe_cast({break_option, {Mod, Line}, action, Action}).

check_action(enable) -> true;
check_action(disable) -> true;
check_action(delete) -> true.

test_at_break(Mod, Line, Function) when is_atom(Mod), is_integer(Line) ->
    check_function(Function),
    dbg_iserver:safe_cast({break_option, {Mod, Line}, condition, Function}).

check_function({Mod, Func}) when is_atom(Mod), is_atom(Func) -> true.

get_binding(Var, Bs) ->
    dbg_icmd:get_binding(Var, Bs).

all_breaks() ->
    dbg_iserver:safe_call(all_breaks).
all_breaks(Mod) when is_atom(Mod) ->
    dbg_iserver:safe_call({all_breaks, Mod}).

%%--------------------------------------------------------------------
%% snapshot() -> [{Pid, Init, Status, Info}]
%%   Pid = pid()
%%   Init = atom()  First interpreted function
%%   Status = idle | running | waiting | break | exit
%%   Info = {} | {Mod, Line} | ExitReason
%%     Mod = atom()
%%     Line = integer()
%%     ExitReason = term()
%%--------------------------------------------------------------------
snapshot() ->
    dbg_iserver:safe_call(snapshot).

%%--------------------------------------------------------------------
%% clear()
%%--------------------------------------------------------------------
clear() ->
    dbg_iserver:safe_cast(clear).
    
%%--------------------------------------------------------------------
%% continue(Pid) -> ok | {error, not_interpreted}
%% continue(X, Y, Z) -> ok | {error, not_interpreted}
%%--------------------------------------------------------------------
continue(Pid) when is_pid(Pid) ->
    case dbg_iserver:safe_call({get_meta, Pid}) of
	{ok, Meta} when is_pid(Meta) ->
	    dbg_icmd:continue(Meta),
	    ok;
	Error ->
	    Error
    end.
    
continue(X, Y, Z) when is_integer(X), is_integer(Y), is_integer(Z) ->
    continue(c:pid(X, Y, Z)).


%%====================================================================
%% External exports only to be used by Debugger
%%====================================================================

%%--------------------------------------------------------------------
%% start()
%% stop()
%% Functions for starting and stopping dbg_iserver explicitly.
%%--------------------------------------------------------------------
start() -> dbg_iserver:start().
stop() ->
    lists:foreach(
      fun(Mod) ->
	      _ = everywhere(distributed,
			     fun() ->
				 erts_debug:breakpoint({Mod,'_','_'}, false)
			     end)
      end,
      interpreted()),
    dbg_iserver:stop().

%%--------------------------------------------------------------------
%% subscribe()
%% Subscribe to information from dbg_iserver. The process calling this
%% function will receive the following messages:
%%   {int, {interpret, Mod}}
%%   {int, {no_interpret, Mod}}
%%   {int, {new_process, {Pid, Function, Status, Info}}}
%%   {int, {new_status, Pid, Status, Info}}
%%   {int, {new_break, {Point, Options}}}
%%   {int, {delete_break, Point}}
%%   {int, {break_options, {Point, Options}}}
%%   {int, no_break}
%%   {int, {no_break, Mod}}
%%   {int, {auto_attach, false|{Flags, Function}}}
%%   {int, {stack_trace, Flag}}
%%--------------------------------------------------------------------
subscribe() -> dbg_iserver:cast({subscribe, self()}).

%%--------------------------------------------------------------------
%% attach(Pid, Function)
%%   Pid = pid()
%%   Function = {Mod, Func} | {Mod, Func, Args} (see auto_attach/2)
%% Tell dbg_iserver to attach to Pid using Function. Will result in:
%%   spawn(Mod, Func, [Pid, Status | Args])
%%--------------------------------------------------------------------
attach(Pid, {Mod, Func}) ->
    attach(Pid, {Mod, Func, []});
attach(Pid, Function) ->
    dbg_iserver:cast({attach, Pid, Function}).

%%--------------------------------------------------------------------
%% step(Pid)
%% next(Pid)
%% (continue(Pid))
%% finish(Pid)
%%--------------------------------------------------------------------
step(Pid) ->
    {ok, Meta} = dbg_iserver:call({get_meta, Pid}),
    dbg_icmd:step(Meta).
next(Pid) ->
    {ok, Meta} = dbg_iserver:call({get_meta, Pid}),
    dbg_icmd:next(Meta).
finish(Pid) ->
    {ok, Meta} = dbg_iserver:call({get_meta, Pid}),
    dbg_icmd:finish(Meta).


%%====================================================================
%% External exports only to be used by an attached process
%%====================================================================

%%--------------------------------------------------------------------
%% attached(Pid) -> {ok, Meta} | error
%%   Pid = Meta = pid()
%% Tell dbg_iserver that I have attached to Pid. dbg_iserver informs
%% the meta process and returns its pid. dbg_iserver may also refuse,
%% if there already is a process attached to Pid.
%%--------------------------------------------------------------------
attached(Pid) ->
    dbg_iserver:call({attached, self(), Pid}).

%%--------------------------------------------------------------------
%% meta(Meta, Cmd)
%%   Meta = pid()
%%   Cmd = step | next | continue | finish | skip | timeout | stop
%%   Cmd = messages => [Message]
%% meta(Meta, Cmd, Arg)
%%   Cmd = trace,       Arg = bool()
%%   Cmd = stack_trace  Arg = all | notail | false
%%   Cmd = stack_frame  Arg = {up|down, Sp}
%%       => {Sp, Mod, Line} | top | bottom
%%   Cmd = backtrace    Arg = integer()
%%       => {Sp, Mod, {Func, Arity}, Line}
%%   Cmd = eval        Arg = {Cm, Cmd} | {Cm, Cmd, Sp}
%%--------------------------------------------------------------------
meta(Meta, step) -> dbg_icmd:step(Meta);
meta(Meta, next) -> dbg_icmd:next(Meta);
meta(Meta, continue) -> dbg_icmd:continue(Meta);
meta(Meta, finish) -> dbg_icmd:finish(Meta);
meta(Meta, skip) -> dbg_icmd:skip(Meta);
meta(Meta, timeout) -> dbg_icmd:timeout(Meta);
meta(Meta, stop) -> dbg_icmd:stop(Meta);
meta(Meta, messages) -> dbg_icmd:get(Meta, messages, null).

meta(Meta, trace, Trace) -> dbg_icmd:set(Meta, trace, Trace);
meta(Meta, stack_trace, Flag) -> dbg_icmd:set(Meta, stack_trace, Flag);
meta(Meta, bindings, Stack) -> dbg_icmd:get(Meta, bindings, Stack);
meta(Meta, stack_frame, Arg) -> dbg_icmd:get(Meta, stack_frame, Arg);
meta(Meta, backtrace, N) -> dbg_icmd:get(Meta, backtrace, N);
meta(Meta, eval, Arg) -> dbg_icmd:eval(Meta, Arg).

%%--------------------------------------------------------------------
%% contents(Mod, Pid) -> string()
%%   Mod = atom()
%%   Pid = pid() | any
%% Return the contents of an interpreted module.
%%--------------------------------------------------------------------
contents(Mod, Pid) ->
    {ok, Bin} = dbg_iserver:call({contents, Mod, Pid}),
    binary_to_list(Bin).

%%--------------------------------------------------------------------
%% functions(Mod) -> [[Name, Arity]]
%%   Mod = Name = atom()
%%   Arity = integer()
%%--------------------------------------------------------------------
functions(Mod) ->
    [F || F <- dbg_iserver:call({functions, Mod}), functions_1(F)].

functions_1([module_info, _Arity]) -> false;
functions_1(_Func) -> true.


%%====================================================================
%% External exports only to be used by error_handler
%%====================================================================

eval(Mod, Func, Args) ->
    dbg_debugged:eval(Mod, Func, Args).


%%====================================================================
%% Internal functions
%%====================================================================

%%--Interpreting modules----------------------------------------------

int_mod({Mod, Src, Beam, BeamBin}, Dist)
  when is_atom(Mod), is_list(Src), is_list(Beam), is_binary(BeamBin) ->
    try
	case is_file(Src) of
	    true ->
		check_application(Src),
		case check_beam(BeamBin) of
		    {ok, Exp, Abst, _BeamBin} ->
			load({Mod, Src, Beam, BeamBin, Exp, Abst}, Dist);
		    error -> 
			error
		end;
	    false ->
		error
	end
    catch
	throw:Reason ->
	    Reason
    end;
int_mod(AbsMod, Dist) when is_atom(AbsMod); is_list(AbsMod) ->
    case check(AbsMod) of
	{ok, Res} -> 
	    load(Res, Dist);
	{error, {app, App}} ->
	    io:format("** Cannot interpret ~p module: ~p~n",
		      [App, AbsMod]),
	    error;
	_Error ->
	    io:format("** Invalid beam file or no abstract code: ~tp\n",
		      [AbsMod]),
	    error
    end.

check(Mod) when is_atom(Mod) -> catch check_module(Mod);
check(File) when is_list(File) -> catch check_file(File).

load({Mod, Src, Beam, BeamBin, Exp, Abst}, Dist) ->
    _ = everywhere(Dist,
		   fun() ->
		       code:purge(Mod),
		       erts_debug:breakpoint({Mod,'_','_'}, false),
		       {module,Mod} = code:load_binary(Mod, Beam, BeamBin)
		   end),
    case erl_prim_loader:get_file(filename:absname(Src)) of
	{ok, SrcBin, _} ->
	    MD5 = code:module_md5(BeamBin),
            SrcBin1 = unicode:characters_to_binary(SrcBin, enc(SrcBin)),
            true = is_binary(SrcBin1),
	    Bin = term_to_binary({interpreter_module,Exp,Abst,SrcBin1,MD5}),
	    {module, Mod} = dbg_iserver:safe_call({load, Mod, Src, Bin}),
	    _ = everywhere(Dist,
			   fun() ->
			       true = erts_debug:breakpoint({Mod,'_','_'}, true) > 0
			   end),
	    {module, Mod};
	error ->
	    error
    end.

check_module(Mod) ->
    case code:which(Mod) of
	Beam when is_list(Beam) ->
	    case find_src(Mod, Beam) of
		Src when is_list(Src) ->
		    check_application(Src),
		    case check_beam(Beam) of
			{ok, Exp, Abst, BeamBin} ->
			    {ok, {Mod, Src, Beam, BeamBin, Exp, Abst}};
			error -> 
			    {error, no_debug_info}
		    end;
		error ->
		    {error, no_src}
	    end;
	_ -> 
	    {error, badarg}
    end.

check_file(Name0) ->
    Src =
	case is_file(Name0) of
	    true -> 
		Name0;
	    false ->
		Name = Name0 ++ ".erl",
		case is_file(Name) of
		    true -> Name;
		    false -> error
		end
	end,
    if
	is_list(Src) ->
	    check_application(Src),
	    Mod = scan_module_name(Src),
	    case find_beam(Mod, Src) of
		Beam when is_list(Beam) ->
		    case check_beam(Beam) of
			{ok, Exp, Abst, BeamBin} ->
			    {ok, {Mod, Src, Beam, BeamBin, Exp, Abst}};
			error ->
			    {error, no_debug_info}
		    end;
		error ->
		    {error, no_beam}
	    end;
	true ->
	    {error, badarg}
    end.

%% Try to avoid interpreting a kernel, stdlib, gs or debugger module.
check_application(Src) ->
    case lists:reverse(filename:split(filename:absname(Src))) of
	[_Mod,"src",AppS|_] ->
	    check_application2(AppS);
	_ -> ok
    end.
check_application2("kernel-"++_) -> throw({error,{app,kernel}});
check_application2("stdlib-"++_) -> throw({error,{app,stdlib}});
check_application2("erts-"++_) -> throw({error,{app,erts}});
check_application2("gs-"++_) -> throw({error,{app,gs}});
check_application2("debugger-"++_) -> throw({error,{app,debugger}});
check_application2(_) -> ok.

find_src(Mod, Beam) ->
    Src0 = filename:rootname(Beam) ++ ".erl",
    case is_file(Src0) of
	true -> Src0;
	false ->
	    EbinDir = filename:dirname(Beam),
	    Src = filename:join([filename:dirname(EbinDir), "src",
				 filename:basename(Src0)]),
	    case is_file(Src) of
		true -> Src;
		false -> find_src_from_module(Mod)
	    end
    end.

find_src_from_module(Mod) ->
    Compile = Mod:module_info(compile),
    case lists:keyfind(source, 1, Compile) of
	{source, Src} ->
	    case is_file(Src) of
		true -> Src;
		false -> error
	    end;
	false ->
	    error
    end.

find_beam(Mod, Src) ->
    SrcDir = filename:dirname(Src),
    BeamFile = atom_to_list(Mod) ++ code:objfile_extension(),
    File = filename:join(SrcDir, BeamFile),
    case is_file(File) of
	true -> File;
	false -> find_beam_1(BeamFile, SrcDir)
    end.

find_beam_1(BeamFile, SrcDir) ->
    RootDir = filename:dirname(SrcDir),
    EbinDir = filename:join(RootDir, "ebin"),
    CodePath = [EbinDir | code:get_path()],
    lists:foldl(fun(_, Beam) when is_list(Beam) -> Beam;
		   (Dir, error) ->
			File = filename:join(Dir, BeamFile),
			case is_file(File) of
			    true -> File;
			    false -> error
			end
		end,
		error,
		CodePath).

check_beam(BeamBin) when is_binary(BeamBin) ->
    case beam_lib:chunks(BeamBin, [abstract_code,exports]) of
	{ok,{_Mod,[{abstract_code,no_abstract_code}|_]}} ->
	    error;
	{ok,{_Mod,[{abstract_code,Abst},{exports,Exp}]}} ->
	    {ok,Exp,Abst, BeamBin};
	_ -> 
	    error
    end;
check_beam(Beam) when is_list(Beam) ->
    {ok, Bin, _FullPath} = erl_prim_loader:get_file(filename:absname(Beam)),
    check_beam(Bin).

is_file(Name) ->
    filelib:is_regular(filename:absname(Name), erl_prim_loader).

everywhere(distributed, Fun) ->
    case is_alive() of
	true -> rpc:multicall(erlang, apply, [Fun,[]]);
	false -> Fun()
    end;
everywhere(local, Fun) ->
    Fun().

scan_module_name(File) ->
    try
        {ok, Bin, _FullPath} =
            erl_prim_loader:get_file(filename:absname(File)),
        scan_module_name_1([], <<>>, Bin, enc(Bin))
    catch
        _:_ ->
            throw({error, no_beam})
    end.

scan_module_name_1(Cont0, B0, Bin0, Enc) ->
    N = min(100, byte_size(Bin0)),
    {Bin1, Bin} = erlang:split_binary(Bin0, N),
    {Chars, B1} =
        case unicode:characters_to_list(list_to_binary([B0, Bin1]), Enc) of
            {incomplete, List, Binary} ->
                {List, Binary};
            List when is_list(List), List =/= [] ->
                {List, <<>>}
        end,
    scan_module_name_2(Cont0, Chars, B1, Bin, Enc).

scan_module_name_2(Cont0, Chars, B1, Bin, Enc) ->
    case erl_scan:tokens(Cont0, Chars, _AnyLine = 1) of
        {done, {ok, Ts, _}, Rest} ->
            scan_module_name_3(Ts, Rest, B1, Bin, Enc);
        {more, Cont} ->
            scan_module_name_1(Cont, B1, Bin, Enc)
    end.

scan_module_name_3([{'-',_},{atom,_,module},{'(',_} | _]=Ts,
                   _Chars, _B1, _Bin, _Enc) ->
    scan_module_name_4(Ts);
scan_module_name_3([{'-',_},{atom,_,_} | _], Chars, B1, Bin, Enc) ->
    scan_module_name_2("", Chars, B1, Bin, Enc).

scan_module_name_4(Ts) ->
    {ok, {attribute,_,module,M}} = erl_parse:parse_form(Ts),
    true = is_atom(M),
    M.

enc(Bin) ->
    case epp:read_encoding_from_binary(Bin) of
        none -> epp:default_encoding();
        Encoding -> Encoding
    end.

%%--Stop interpreting modules-----------------------------------------

del_mod(AbsMod, Dist) ->
    Mod = if
	      is_atom(AbsMod) -> AbsMod;
	      is_list(AbsMod) ->
		  list_to_atom(filename:basename(AbsMod,".erl"))
	  end,
    dbg_iserver:safe_cast({delete, Mod}),
    _ = everywhere(Dist,
		   fun() ->
		       erts_debug:breakpoint({Mod,'_','_'}, false),
		       erlang:yield()
		   end),
    ok.
