%%
%% %CopyrightBegin%
%%
%% Copyright Ericsson AB 1998-2018. 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(dbg_ieval).
-export([eval/3,exit_info/5]).
-export([eval_expr/3]).
-export([check_exit_msg/3,exception/4]).
-include("dbg_ieval.hrl").
%%====================================================================
%% External exports
%%====================================================================
%%--------------------------------------------------------------------
%% eval(Mod, Func, Args) -> Meta
%% Mod = Func = atom()
%% Args = [term()]
%% MFA = {Mod,Func,Args} | {Mod,Func,Arity} | {Fun,Args}
%% Arity = integer()
%% Meta = pid()
%% Entry point from debugged process (dbg_debugged).
%% Immediately returns the pid for the meta process.
%% The evaluated value will later be sent as a message to
%% the calling process.
%%--------------------------------------------------------------------
eval(Mod, Func, Args) ->
Debugged = self(),
Int = dbg_iserver:find(),
case dbg_iserver:call(Int, {get_meta,Debugged}) of
{ok,Meta} ->
Meta ! {re_entry, Debugged, {eval,{Mod,Func,Args}}},
Meta;
{error,not_interpreted} ->
spawn(fun() ->
meta(Int, Debugged, Mod, Func, Args)
end)
end.
%%--------------------------------------------------------------------
%% exit_info(Int, AttPid, OrigPid, Reason, ExitInfo)
%% Int = AttPid = OrigPid = pid()
%% Reason = term()
%% ExitInfo = {{Mod,Line}, Bs, Stack} | {}
%% Meta process started when attaching to a terminated process.
%% Spawned (by dbg_iserver) in response to user request.
%%--------------------------------------------------------------------
exit_info(Int, AttPid, OrigPid, Reason, ExitInfo) ->
put(int, Int),
put(attached, AttPid),
put(breakpoints, dbg_iserver:call(Int, all_breaks)),
put(self, OrigPid),
put(exit_info, ExitInfo),
case ExitInfo of
{{Mod,Line},Bs,S} ->
dbg_istk:from_external(S),
Le = dbg_istk:stack_level(),
dbg_icmd:tell_attached({exit_at, {Mod, Line}, Reason, Le}),
exit_loop(OrigPid, Reason, Bs,#ieval{module=Mod,line=Line});
{} ->
dbg_istk:init(),
dbg_icmd:tell_attached({exit_at, null, Reason, 1}),
exit_loop(OrigPid, Reason, erl_eval:new_bindings(),#ieval{})
end.
%%--------------------------------------------------------------------
%% eval_expr(Expr, Bs, Ieval) -> {value, Value, Bs}
%%
%% Evalute a shell expression in the real process.
%% Called (dbg_icmd) in response to a user request.
%%--------------------------------------------------------------------
eval_expr(Expr, Bs, Ieval) ->
%% Save current exit info
ExitInfo = get(exit_info),
Stacktrace = get(stacktrace),
%% Emulate a surrounding catch
try debugged_cmd({eval,Expr,Bs}, Bs, Ieval)
catch
Class:Reason ->
Result = case Class of
throw -> Reason;
_ -> {'EXIT', Reason}
end,
%% Reset exit info
put(exit_info, ExitInfo),
put(stacktrace, Stacktrace),
{value, Result, Bs}
end.
%%--------------------------------------------------------------------
%% check_exit_msg(Msg, Bs, Ieval)
%% Msg = term()
%% Check if Msg is an 'EXIT' msg from the iserver or a 'DOWN' msg
%% from the debugged process. If so exit with correct reason.
%%--------------------------------------------------------------------
check_exit_msg({'EXIT', Int, Reason}, _Bs, #ieval{level=Le}) ->
%% This *must* be interpreter which has terminated,
%% we are not linked to anyone else
if
Le =:= 1 ->
exit(Reason);
Le > 1 ->
exit({Int, Reason})
end;
check_exit_msg({'DOWN',_,_,_,Reason}, Bs,
#ieval{level=Le, module=Mod, line=Li}) ->
%% This *must* be Debugged which has terminated,
%% we are not monitoring anyone else
%% Inform Int about current position, bindings and stack
ExitInfo =
case get(exit_info) of
%% Debugged has been terminated by someone
%% - really the position, bindings and stack are of no
%% importance in this case
%% If we don't save them, however, post-mortem analysis
%% of the process isn't possible
undefined when Le =:= 1 -> % died outside interpreted code
{};
undefined when Le > 1 ->
StackExternal = (dbg_istk:delayed_to_external())(),
{{Mod, Li}, Bs, StackExternal};
%% Debugged has terminated due to an exception
ExitInfo0 when is_function(ExitInfo0, 0) ->
ExitInfo0()
end,
dbg_iserver:cast(get(int), {set_exit_info,self(),ExitInfo}),
if
Le =:= 1 ->
exit(Reason);
Le > 1 ->
exit({get(self), Reason})
end;
check_exit_msg(_Msg, _Bs, _Ieval) ->
ignore.
%%--------------------------------------------------------------------
%% exception(Class, Reason, Bs, Ieval)
%% Class = error | exit | throw
%% Reason = term()
%% Bs = bindings()
%% Ieval = #ieval{}
%% Store information about where in the code the error is located
%% and then raise the exception.
%%--------------------------------------------------------------------
exception(Class, Reason, Bs, Ieval) ->
exception(Class, Reason, Bs, Ieval, false).
exception(Class, Reason, Bs, Ieval, false) ->
do_exception(Class, Reason,
dbg_istk:delayed_stacktrace(no_args, Ieval),
Bs, Ieval);
exception(Class, Reason, Bs, Ieval, true) ->
do_exception(Class, Reason,
dbg_istk:delayed_stacktrace(include_args, Ieval),
Bs, Ieval).
do_exception(Class, Reason, Stacktrace, Bs, #ieval{module=M, line=Line}) ->
StackFun = dbg_istk:delayed_to_external(),
ExitInfo = fun() ->
{{M,Line},Bs,StackFun()}
end,
put(exit_info, ExitInfo),
put(stacktrace, Stacktrace),
erlang:Class(Reason).
%%====================================================================
%% Internal functions
%%====================================================================
%%--Loops-------------------------------------------------------------
%% Entry point for first-time initialization of meta process
meta(Int, Debugged, M, F, As) ->
process_flag(trap_exit, true),
erlang:monitor(process, Debugged),
%% Inform dbg_iserver, get the initial status in return
Pargs = case {M, F} of
%% If it's a fun we're evaluating, show a text
%% representation of the fun and its arguments,
%% not dbg_ieval:eval_fun(...)
{dbg_ieval, EvalFun} when EvalFun =:= eval_fun;
EvalFun =:= eval_named_fun ->
{Mx, Fx} = lists:last(As),
{Mx, Fx, lists:nth(2, As)};
_ ->
{M, F, As}
end,
Status = dbg_iserver:call(Int, {new_process,Debugged,self(),Pargs}),
%% Initiate process dictionary
put(int, Int), % pid() dbg_iserver
put(attached, undefined),% pid() attached process
put(breakpoints, dbg_iserver:call(Int, all_breaks)),
put(cache, []),
put(next_break, Status), % break | running (other values later)
put(self, Debugged), % pid() interpreted process
dbg_istk:init(),
put(stacktrace, []),
put(trace_stack, dbg_iserver:call(Int, get_stack_trace)),
put(trace, false), % bool() Trace on/off
put(user_eval, []),
%% Send the result of the meta process
Ieval = #ieval{},
Debugged ! {sys, self(), eval_mfa(Debugged,M,F,As,Ieval)},
dbg_iserver:cast(Int, {set_status, self(), idle, {}}),
dbg_icmd:tell_attached(idle),
meta_loop(Debugged, erl_eval:new_bindings(), Ieval).
debugged_cmd(Cmd, Bs, Ieval) ->
Debugged = get(self),
Debugged ! {sys, self(), {command,Cmd}},
meta_loop(Debugged, Bs, Ieval).
meta_loop(Debugged, Bs, #ieval{level=Le} = Ieval) ->
receive
%% The following messages can only be received when Meta is
%% waiting for Debugged to evaluate non-interpreted code
%% or a Bif. Le>1
{sys, Debugged, {value,Val}} ->
{value, Val, Bs};
{sys, Debugged, {value,Val,Bs2}} ->
{value, Val, merge_bindings(Bs2, Bs, Ieval)};
{sys, Debugged, {exception,{Class,Reason,Stk}}} ->
case get(exit_info) of
%% Error occurred outside of interpreted code.
undefined ->
MakeStk0 = dbg_istk:delayed_stacktrace(),
MakeStk = fun(Depth0) ->
Depth = max(0, Depth0 - length(Stk)),
Stk ++ MakeStk0(Depth)
end,
do_exception(Class, Reason, MakeStk, Bs, Ieval);
%% Error must have occured within a re-entry to
%% interpreted code, simply raise the exception
_ ->
erlang:Class(Reason)
end;
%% Re-entry to Meta from non-interpreted code
{re_entry, Debugged, {eval,{M,F,As}}} when Le =:= 1 ->
%% Reset process dictionary
%% This is really only necessary if the process left
%% interpreted code at a call level > 1
dbg_istk:init(),
put(stacktrace, []),
put(exit_info, undefined),
dbg_iserver:cast(get(int), {set_status,self(),running,{}}),
dbg_icmd:tell_attached(running),
%% Tell attached process(es) to update source code.
dbg_icmd:tell_attached({re_entry,M,F}),
%% Send the result of the meta process
Debugged ! {sys,self(),eval_mfa(Debugged,M,F,As,Ieval)},
dbg_iserver:cast(get(int), {set_status,self(),idle,{}}),
dbg_icmd:tell_attached(idle),
meta_loop(Debugged, Bs, Ieval);
%% Evaluation in Debugged results in call to interpreted
%% function (probably? a fun)
{re_entry, Debugged, {eval,{M,F,As}}} when Le>1 ->
Ieval2 = Ieval#ieval{module=undefined, line=-1},
Debugged ! {sys,self(),eval_mfa(Debugged,M,F,As,Ieval2)},
meta_loop(Debugged, Bs, Ieval);
Msg ->
check_exit_msg(Msg, Bs, Ieval),
dbg_icmd:handle_msg(Msg, idle, Bs, Ieval),
meta_loop(Debugged, Bs, Ieval)
end.
exit_loop(OrigPid, Reason, Bs, Ieval) ->
receive
Msg ->
check_exit_msg(Msg, Bs, Ieval),
dbg_icmd:handle_msg(Msg, exit_at, Bs, Ieval),
exit_loop(OrigPid, Reason, Bs, Ieval)
end.
%%--Trace function----------------------------------------------------
%%--------------------------------------------------------------------
%% trace(What, Args)
%% What = send | receivex | received | call | return | bif
%% Args depends on What, see the code.
%%--------------------------------------------------------------------
trace(What, Args) ->
trace(What, Args, get(trace)).
trace(return, {_Le,{dbg_apply,_,_,_}}, _Bool) ->
ignore;
trace(What, Args, true) ->
Fun = fun(P) -> format_trace(What, Args, P) end,
dbg_icmd:tell_attached({trace_output, Fun});
trace(_What, _Args, false) ->
ignore.
format_trace(What, Args, P) ->
case What of
send ->
{To,Msg} = Args,
io_lib:format("==> ~w : "++P++"~n", [To, Msg]);
receivex ->
{Le, TimeoutP} = Args,
Tail = case TimeoutP of
true -> "with timeout~n";
false -> "~n"
end,
io_lib:format(" (~w) receive " ++ Tail, [Le]);
received when Args =:= null ->
io_lib:format("~n", []);
received -> % Args=Msg
io_lib:format("~n<== "++P++"~n", [Args]);
call ->
{Called, {Le,Li,M,F,As}} = Args,
case Called of
extern ->
io_lib:format("++ (~w) <~w> ~w:~tw~ts~n",
[Le,Li,M,F,format_args(As, P)]);
local ->
io_lib:format("++ (~w) <~w> ~tw~ts~n",
[Le,Li,F,format_args(As, P)])
end;
call_fun ->
{Le,Li,F,As} = Args,
io_lib:format("++ (~w) <~w> ~tw~ts~n",
[Le, Li, F, format_args(As, P)]);
return ->
{Le,Val} = Args,
io_lib:format("-- (~w) "++P++"~n", [Le, Val]);
bif ->
{Le,Li,M,F,As} = Args,
io_lib:format("++ (~w) <~w> ~w:~tw~ts~n",
[Le, Li, M, F, format_args(As, P)])
end.
format_args(As, P) when is_list(As) ->
[$(,format_args1(As, P),$)];
format_args(A, P) ->
[$/,io_lib:format(P, [A])].
format_args1([A], P) ->
[io_lib:format(P, [A])];
format_args1([A|As], P) ->
[io_lib:format(P, [A]),$,|format_args1(As, P)];
format_args1([], _) ->
[].
%%--Other useful functions--------------------------------------------
%% Mimic catch behaviour
catch_value(error, Reason) ->
{'EXIT',{Reason,get_stacktrace()}};
catch_value(exit, Reason) ->
{'EXIT',Reason};
catch_value(throw, Reason) ->
Reason.
%%--Code interpretation-----------------------------------------------
%%--------------------------------------------------------------------
%% Top level function of meta evaluator.
%% Return message to be replied to the target process.
%%--------------------------------------------------------------------
eval_mfa(Debugged, M, F, As, #ieval{level=Le}=Ieval0) ->
Int = get(int),
Bs = erl_eval:new_bindings(),
Ieval = Ieval0#ieval{level=Le+1,top=true},
try do_eval_function(M, F, As, Bs, extern, Ieval) of
{value, Val, _Bs} ->
trace(return, {Le,Val}),
{ready, Val}
catch
exit:{Debugged, Reason} ->
exit(Reason);
exit:{Int, Reason} ->
exit(Reason);
Class:Reason ->
{exception, {Class, Reason, get_stacktrace()}}
end.
eval_function(Mod, Name, As, Bs, Called, Ieval0, Lc) ->
Tail = Lc andalso get(trace_stack) =:= no_tail,
case Tail of
false ->
Ieval = dbg_istk:push(Bs, Ieval0, Lc),
{value,Val,_} = do_eval_function(Mod, Name, As, Bs, Called, Ieval),
dbg_istk:pop(),
trace(return, {Ieval#ieval.level,Val}),
{value,Val,Bs};
true ->
do_eval_function(Mod, Name, As, Bs, Called, Ieval0)
end.
do_eval_function(Mod, Fun, As0, Bs0, _, Ieval0) when is_function(Fun);
Mod =:= ?MODULE,
Fun =:= eval_fun orelse
Fun =:= eval_named_fun ->
#ieval{level=Le,line=Li,top=Top} = Ieval0,
case lambda(Fun, As0) of
{[{clause,Fc,_,_,_}|_]=Cs,Module,Name,As,Bs} ->
Ieval = Ieval0#ieval{module=Module,function=Name,
arguments=As0,line=Fc},
trace(call_fun, {Le,Li,Name,As}),
fnk_clauses(Cs, As, Bs, Ieval);
not_interpreted when Top -> % We are leaving interpreted code
trace(call_fun, {Le,Li,Fun,As0}),
{value, {dbg_apply,erlang,apply,[Fun,As0]}, Bs0};
not_interpreted ->
trace(call_fun, {Le,Li,Fun,As0}),
debugged_cmd({apply,erlang,apply,[Fun,As0]}, Bs0, Ieval0);
{error,Reason} ->
%% It's ok not to push anything in this case, the error
%% reason contains information about the culprit
%% ({badarity,{{Mod,Name},As}})
exception(error, Reason, Bs0, Ieval0)
end;
do_eval_function(Mod, Name, As0, Bs0, Called, Ieval0) ->
#ieval{level=Le,line=Li,top=Top} = Ieval0,
trace(call, {Called, {Le,Li,Mod,Name,As0}}),
Ieval = Ieval0#ieval{module=Mod,function=Name,arguments=As0},
case get_function(Mod, Name, As0, Called) of
[{clause,FcLine,_,_,_}|_]=Cs ->
fnk_clauses(Cs, As0, erl_eval:new_bindings(),
Ieval#ieval{line=FcLine});
not_interpreted when Top -> % We are leaving interpreted code
{value, {dbg_apply,Mod,Name,As0}, Bs0};
not_interpreted ->
debugged_cmd({apply,Mod,Name,As0}, Bs0, Ieval);
undef ->
exception(error, undef, Bs0, Ieval, true)
end.
lambda(eval_fun, [Cs,As,Bs,{Mod,Name}=F]) ->
%% Fun defined in interpreted code, called from outside
if
length(element(3,hd(Cs))) =:= length(As) ->
db_ref(Mod), %% Adds ref between module and process
{Cs,Mod,Name,As,Bs};
true ->
{error,{badarity,{F,As}}}
end;
lambda(eval_named_fun, [Cs,As,Bs0,FName,RF,{Mod,Name}=F]) ->
%% Fun defined in interpreted code, called from outside
if
length(element(3,hd(Cs))) =:= length(As) ->
db_ref(Mod), %% Adds ref between module and process
Bs1 = add_binding(FName, RF, Bs0),
{Cs,Mod,Name,As,Bs1};
true ->
{error,{badarity,{F,As}}}
end;
lambda(Fun, As) when is_function(Fun) ->
%% Fun called from within interpreted code...
case erlang:fun_info(Fun, module) of
%% ... and the fun was defined in interpreted code
{module, ?MODULE} ->
{Mod,Name,Bs, Cs} =
case erlang:fun_info(Fun, env) of
{env,[{{M,F},Bs0,Cs0}]} ->
{M,F,Bs0, Cs0};
{env,[{{M,F},Bs0,Cs0,FName}]} ->
{M,F,add_binding(FName, Fun, Bs0), Cs0}
end,
{arity, Arity} = erlang:fun_info(Fun, arity),
if
length(As) =:= Arity ->
db_ref(Mod), %% Adds ref between module and process
{Cs,Mod,Name,As,Bs};
true ->
{error,{badarity,{Fun,As}}}
end;
%% ... and the fun was defined outside interpreted code
_ ->
not_interpreted
end.
get_function(Mod, Name, Args, local) ->
Arity = length(Args),
Key = {Mod,Name,Arity},
case cached(Key) of
false ->
DbRef = db_ref(Mod),
case dbg_idb:match_object(DbRef, {{Mod,Name,Arity,'_'},'_'}) of
[{{Mod,Name,Arity,Exp},Clauses}] ->
cache(Key, {Exp,Clauses}),
Clauses;
_ -> undef
end;
{_Exp,Cs} -> Cs
end;
get_function(Mod, Name, Args, extern) ->
Arity = length(Args),
Key = {Mod,Name,Arity},
case cached(Key) of
false ->
case db_ref(Mod) of
not_found -> not_interpreted;
DbRef ->
case dbg_idb:lookup(DbRef, {Mod,Name,Arity,true}) of
{ok,Data} ->
cache(Key, {true,Data}),
Data;
not_found ->
case dbg_idb:lookup(DbRef, module) of
{ok,_} -> undef;
not_found -> not_interpreted
end
end
end;
{true,Cs} -> Cs;
{false,_} -> undef
end.
db_ref(Mod) ->
case get(?DB_REF_KEY(Mod)) of
undefined ->
case dbg_iserver:call(get(int),
{get_module_db, Mod, get(self)}) of
not_found ->
not_found;
ModDb ->
Node = node(get(int)),
DbRef = if
Node =/= node() -> {Node,ModDb};
true -> ModDb
end,
put(?DB_REF_KEY(Mod), DbRef),
DbRef
end;
DbRef ->
DbRef
end.
cache(Key, Data) ->
put(cache, lists:sublist([{Key,Data}|get(cache)], 5)).
cached(Key) ->
case lists:keyfind(Key, 1, get(cache)) of
{Key,Data} -> Data;
false -> false
end.
%% Try to find a matching function clause
%% #ieval.level is set, the other fields must be set in this function
fnk_clauses([{clause,Line,Pars,Gs,Body}|Cs], As, Bs0, Ieval) ->
case head_match(Pars, As, [], Bs0) of
{match,Bs1} ->
Bs = add_bindings(Bs1, Bs0),
case guard(Gs, Bs) of
true ->
seq(Body, Bs, Ieval#ieval{line=Line});
false ->
fnk_clauses(Cs, As, Bs0, Ieval)
end;
nomatch ->
fnk_clauses(Cs, As, Bs0, Ieval)
end;
fnk_clauses([], _As, Bs, Ieval) ->
exception(error, function_clause, Bs, Ieval, true).
seq([E], Bs0, Ieval) ->
case dbg_icmd:cmd(E, Bs0, Ieval) of
{skip,Bs} ->
{value,skipped,Bs};
Bs ->
expr(E, Bs, Ieval)
end;
seq([E|Es], Bs0, Ieval) ->
case dbg_icmd:cmd(E, Bs0, Ieval) of
{skip,Bs} ->
seq(Es, Bs, Ieval);
Bs1 ->
{value,_,Bs} = expr(E, Bs1, Ieval#ieval{top=false}),
seq(Es, Bs, Ieval)
end;
seq([], Bs, _) ->
{value,true,Bs}.
%% Variable
expr({var,Line,V}, Bs, Ieval) ->
case binding(V, Bs) of
{value,Val} ->
{value,Val,Bs};
unbound ->
exception(error, {unbound,V}, Bs, Ieval#ieval{line=Line})
end;
expr({value,_,Val}, Bs, _Ieval) ->
{value,Val,Bs};
expr({value,Val}, Bs, _Ieval) -> % Special case straight values
{value,Val,Bs};
%% List
expr({cons,Line,H0,T0}, Bs0, Ieval0) ->
Ieval = Ieval0#ieval{line=Line,top=false},
{value,H,Bs1} = expr(H0, Bs0, Ieval),
{value,T,Bs2} = expr(T0, Bs0, Ieval),
{value,[H|T],merge_bindings(Bs2, Bs1, Ieval)};
%% Tuple
expr({tuple,Line,Es0}, Bs0, Ieval) ->
{Vs,Bs} = eval_list(Es0, Bs0, Ieval#ieval{line=Line}),
{value,list_to_tuple(Vs),Bs};
%% Map
expr({map,Line,Fs}, Bs0, Ieval) ->
{Map,Bs} = eval_new_map_fields(Fs, Bs0, Ieval#ieval{line=Line,top=false},
fun expr/3),
{value,Map,Bs};
expr({map,Line,E0,Fs0}, Bs0, Ieval0) ->
Ieval = Ieval0#ieval{line=Line,top=false},
{value,E,Bs1} = expr(E0, Bs0, Ieval),
{Fs,Bs2} = eval_map_fields(Fs0, Bs0, Ieval),
_ = maps:put(k, v, E), %Validate map.
Value = lists:foldl(fun ({map_assoc,K,V}, Mi) -> maps:put(K,V,Mi);
({map_exact,K,V}, Mi) -> maps:update(K,V,Mi)
end, E, Fs),
{value,Value,merge_bindings(Bs2, Bs1, Ieval)};
%% A block of statements
expr({block,Line,Es},Bs,Ieval) ->
seq(Es, Bs, Ieval#ieval{line=Line});
%% Catch statement
expr({'catch',Line,Expr}, Bs0, Ieval) ->
try expr(Expr, Bs0, Ieval#ieval{line=Line, top=false})
catch
Class:Reason ->
%% Exception caught, reset exit info
put(exit_info, undefined),
dbg_istk:pop(Ieval#ieval.level),
Value = catch_value(Class, Reason),
trace(return, {Ieval#ieval.level,Value}),
{value, Value, Bs0}
end;
%% Try-catch statement
expr({'try',Line,Es,CaseCs,CatchCs,[]}, Bs0, Ieval0) ->
Ieval = Ieval0#ieval{line=Line},
try seq(Es, Bs0, Ieval#ieval{top=false}) of
{value,Val,Bs} = Value ->
case CaseCs of
[] -> Value;
_ ->
case_clauses(Val, CaseCs, Bs, try_clause, Ieval)
end
catch
Class:Reason when CatchCs =/= [] ->
catch_clauses({Class,Reason,[]}, CatchCs, Bs0, Ieval)
end;
expr({'try',Line,Es,CaseCs,CatchCs,As}, Bs0, Ieval0) ->
Ieval = Ieval0#ieval{line=Line},
try seq(Es, Bs0, Ieval#ieval{top=false}) of
{value,Val,Bs} = Value ->
case CaseCs of
[] -> Value;
_ ->
case_clauses(Val, CaseCs, Bs, try_clause, Ieval)
end
catch
Class:Reason when CatchCs =/= [] ->
catch_clauses({Class,Reason,[]}, CatchCs, Bs0, Ieval)
after
seq(As, Bs0, Ieval#ieval{top=false})
end;
%% Case statement
expr({'case',Line,E,Cs}, Bs0, Ieval) ->
{value,Val,Bs} =
expr(E, Bs0, Ieval#ieval{line=Line, top=false}),
case_clauses(Val, Cs, Bs, case_clause, Ieval#ieval{line=Line});
%% If statement
expr({'if',Line,Cs}, Bs, Ieval) ->
if_clauses(Cs, Bs, Ieval#ieval{line=Line});
%% Andalso/orelse
expr({'andalso',Line,E1,E2}, Bs0, Ieval) ->
case expr(E1, Bs0, Ieval#ieval{line=Line, top=false}) of
{value,false,_}=Res ->
Res;
{value,true,Bs} ->
{value,Val,_} = expr(E2, Bs, Ieval#ieval{line=Line, top=false}),
{value,Val,Bs};
{value,Val,Bs} ->
exception(error, {badarg,Val}, Bs, Ieval)
end;
expr({'orelse',Line,E1,E2}, Bs0, Ieval) ->
case expr(E1, Bs0, Ieval#ieval{line=Line, top=false}) of
{value,true,_}=Res ->
Res;
{value,false,Bs} ->
{value,Val,_} = expr(E2, Bs, Ieval#ieval{line=Line, top=false}),
{value,Val,Bs};
{value,Val,Bs} ->
exception(error, {badarg,Val}, Bs, Ieval)
end;
%% Matching expression
expr({match,Line,Lhs,Rhs0}, Bs0, Ieval0) ->
Ieval = Ieval0#ieval{line=Line},
{value,Rhs,Bs1} = expr(Rhs0, Bs0, Ieval#ieval{top=false}),
case match(Lhs, Rhs, Bs1) of
{match,Bs} ->
{value,Rhs,Bs};
nomatch ->
exception(error, {badmatch,Rhs}, Bs1, Ieval)
end;
%% Construct a fun
expr({make_fun,Line,Name,Cs}, Bs, #ieval{module=Module}=Ieval) ->
Arity = length(element(3,hd(Cs))),
Info = {{Module,Name},Bs,Cs},
Fun =
case Arity of
0 -> fun() -> eval_fun([], Info) end;
1 -> fun(A) -> eval_fun([A], Info) end;
2 -> fun(A,B) -> eval_fun([A,B], Info) end;
3 -> fun(A,B,C) -> eval_fun([A,B,C], Info) end;
4 -> fun(A,B,C,D) -> eval_fun([A,B,C,D], Info) end;
5 -> fun(A,B,C,D,E) -> eval_fun([A,B,C,D,E], Info) end;
6 -> fun(A,B,C,D,E,F) -> eval_fun([A,B,C,D,E,F], Info) end;
7 -> fun(A,B,C,D,E,F,G) ->
eval_fun([A,B,C,D,E,F,G], Info) end;
8 -> fun(A,B,C,D,E,F,G,H) ->
eval_fun([A,B,C,D,E,F,G,H], Info) end;
9 -> fun(A,B,C,D,E,F,G,H,I) ->
eval_fun([A,B,C,D,E,F,G,H,I], Info) end;
10 -> fun(A,B,C,D,E,F,G,H,I,J) ->
eval_fun([A,B,C,D,E,F,G,H,I,J], Info) end;
11 -> fun(A,B,C,D,E,F,G,H,I,J,K) ->
eval_fun([A,B,C,D,E,F,G,H,I,J,K], Info) end;
12 -> fun(A,B,C,D,E,F,G,H,I,J,K,L) ->
eval_fun([A,B,C,D,E,F,G,H,I,J,K,L], Info) end;
13 -> fun(A,B,C,D,E,F,G,H,I,J,K,L,M) ->
eval_fun([A,B,C,D,E,F,G,H,I,J,K,L,M], Info) end;
14 -> fun(A,B,C,D,E,F,G,H,I,J,K,L,M,N) ->
eval_fun([A,B,C,D,E,F,G,H,I,J,K,L,M,N], Info) end;
15 -> fun(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O) ->
eval_fun([A,B,C,D,E,F,G,H,I,J,K,L,M,N,O], Info) end;
16 -> fun(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P) ->
eval_fun([A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P], Info) end;
17 -> fun(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q) ->
eval_fun([A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q], Info) end;
18 -> fun(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R) ->
eval_fun([A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R], Info) end;
19 -> fun(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S) ->
eval_fun([A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S],Info) end;
20 -> fun(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T) ->
eval_fun([A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T],Info) end;
_Other ->
exception(error, {'argument_limit',{'fun',Cs}}, Bs,
Ieval#ieval{line=Line})
end,
{value,Fun,Bs};
%% Construct a fun
expr({make_named_fun,Line,Name,FName,Cs}, Bs, #ieval{module=Module}=Ieval) ->
Arity = length(element(3,hd(Cs))),
Info = {{Module,Name},Bs,Cs,FName},
Fun =
case Arity of
0 -> fun RF() -> eval_named_fun([], RF, Info) end;
1 -> fun RF(A) -> eval_named_fun([A], RF, Info) end;
2 -> fun RF(A,B) ->
eval_named_fun([A,B], RF, Info) end;
3 -> fun RF(A,B,C) ->
eval_named_fun([A,B,C], RF, Info) end;
4 -> fun RF(A,B,C,D) ->
eval_named_fun([A,B,C,D], RF, Info) end;
5 -> fun RF(A,B,C,D,E) ->
eval_named_fun([A,B,C,D,E],
RF, Info) end;
6 -> fun RF(A,B,C,D,E,F) ->
eval_named_fun([A,B,C,D,E,F],
RF, Info) end;
7 -> fun RF(A,B,C,D,E,F,G) ->
eval_named_fun([A,B,C,D,E,F,G],
RF, Info) end;
8 -> fun RF(A,B,C,D,E,F,G,H) ->
eval_named_fun([A,B,C,D,E,F,G,H],
RF, Info) end;
9 -> fun RF(A,B,C,D,E,F,G,H,I) ->
eval_named_fun([A,B,C,D,E,F,G,H,I],
RF, Info) end;
10 -> fun RF(A,B,C,D,E,F,G,H,I,J) ->
eval_named_fun([A,B,C,D,E,F,G,H,I,J],
RF, Info) end;
11 -> fun RF(A,B,C,D,E,F,G,H,I,J,K) ->
eval_named_fun([A,B,C,D,E,F,G,H,I,J,K],
RF, Info) end;
12 -> fun RF(A,B,C,D,E,F,G,H,I,J,K,L) ->
eval_named_fun([A,B,C,D,E,F,G,H,I,J,K,L],
RF, Info) end;
13 -> fun RF(A,B,C,D,E,F,G,H,I,J,K,L,M) ->
eval_named_fun([A,B,C,D,E,F,G,H,I,J,K,L,M],
RF, Info) end;
14 -> fun RF(A,B,C,D,E,F,G,H,I,J,K,L,M,N) ->
eval_named_fun([A,B,C,D,E,F,G,H,I,J,K,L,M,N],
RF, Info) end;
15 -> fun RF(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O) ->
eval_named_fun([A,B,C,D,E,F,G,H,I,J,K,L,M,N,O],
RF, Info) end;
16 -> fun RF(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P) ->
eval_named_fun([A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P],
RF, Info) end;
17 -> fun RF(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q) ->
eval_named_fun([A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q],
RF, Info) end;
18 -> fun RF(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R) ->
eval_named_fun([A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,
R],
RF, Info) end;
19 -> fun RF(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S) ->
eval_named_fun([A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,
R,S],
RF, Info) end;
20 -> fun RF(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T) ->
eval_named_fun([A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,
R,S,T],
RF, Info) end;
_Other ->
exception(error, {'argument_limit',{named_fun,FName,Cs}}, Bs,
Ieval#ieval{line=Line})
end,
{value,Fun,Bs};
%% Construct an external fun.
expr({make_ext_fun,Line,MFA0}, Bs0, Ieval0) ->
{[M,F,A],Bs} = eval_list(MFA0, Bs0, Ieval0),
try erlang:make_fun(M, F, A) of
Value ->
{value,Value,Bs}
catch
error:badarg ->
Ieval1 = Ieval0#ieval{line=Line},
Ieval2 = dbg_istk:push(Bs0, Ieval1, false),
Ieval = Ieval2#ieval{module=erlang,function=make_fun,
arguments=[M,F,A],line=-1},
exception(error, badarg, Bs, Ieval, true)
end;
%% Local function call
expr({local_call,Line,F,As0,Lc}, Bs0, #ieval{module=M} = Ieval0) ->
Ieval = Ieval0#ieval{line=Line},
{As,Bs} = eval_list(As0, Bs0, Ieval),
eval_function(M, F, As, Bs, local, Ieval, Lc);
%% Remote function call
expr({call_remote,Line,M,F,As0,Lc}, Bs0, Ieval0) ->
Ieval = Ieval0#ieval{line=Line},
{As,Bs} = eval_list(As0, Bs0, Ieval),
eval_function(M, F, As, Bs, extern, Ieval, Lc);
%% Emulated semantics of some BIFs
expr({dbg,Line,self,[]}, Bs, #ieval{level=Le}) ->
trace(bif, {Le,Line,erlang,self,[]}),
Self = get(self),
trace(return, {Le,Self}),
{value,Self,Bs};
expr({dbg,Line,get_stacktrace,[]}, Bs, #ieval{level=Le}) ->
trace(bif, {Le,Line,erlang,get_stacktrace,[]}),
Stacktrace = get_stacktrace(),
trace(return, {Le,Stacktrace}),
{value,Stacktrace,Bs};
expr({dbg,Line,raise,As0}, Bs0, #ieval{level=Le}=Ieval0) ->
%% Since erlang:get_stacktrace/0 is emulated, we will
%% need to emulate erlang:raise/3 too so that we can
%% capture the stacktrace.
Ieval = Ieval0#ieval{line=Line},
{[Class,Reason,Stk0]=As,Bs} = eval_list(As0, Bs0, Ieval),
trace(bif, {Le,Line,erlang,raise,As}),
try
%% Evaluate raise/3 for error checking and
%% truncating of the stacktrace to the correct depth.
Error = erlang:raise(Class, Reason, Stk0),
trace(return, {Le,Error}),
{value,Error,Bs}
catch
_:_:Stk -> %Possibly truncated.
StkFun = fun(_) -> Stk end,
do_exception(Class, Reason, StkFun, Bs, Ieval)
end;
expr({dbg,Line,throw,As0}, Bs0, #ieval{level=Le}=Ieval0) ->
Ieval = Ieval0#ieval{line=Line},
{[Term],Bs} = eval_list(As0, Bs0, Ieval),
trace(bif, {Le,Line,erlang,throw,[Term]}),
exception(throw, Term, Bs, Ieval);
expr({dbg,Line,error,As0}, Bs0, #ieval{level=Le}=Ieval0) ->
Ieval = Ieval0#ieval{line=Line},
{[Term],Bs} = eval_list(As0, Bs0, Ieval),
trace(bif, {Le,Line,erlang,error,[Term]}),
exception(error, Term, Bs, Ieval);
expr({dbg,Line,exit,As0}, Bs0, #ieval{level=Le}=Ieval0) ->
Ieval = Ieval0#ieval{line=Line},
{[Term],Bs} = eval_list(As0, Bs0, Ieval),
trace(bif, {Le,Line,erlang,exit,[Term]}),
exception(exit, Term, Bs, Ieval);
%% Call to "safe" BIF, ie a BIF that can be executed in Meta process
expr({safe_bif,Line,M,F,As0}, Bs0, #ieval{level=Le}=Ieval0) ->
Ieval1 = Ieval0#ieval{line=Line},
{As,Bs} = eval_list(As0, Bs0, Ieval1),
trace(bif, {Le,Line,M,F,As}),
Ieval2 = dbg_istk:push(Bs0, Ieval1, false),
Ieval = Ieval2#ieval{module=M,function=F,arguments=As,line=-1},
{_,Value,_} = Res = safe_bif(M, F, As, Bs, Ieval),
trace(return, {Le,Value}),
dbg_istk:pop(),
Res;
%% Call to a BIF that must be evaluated in the correct process
expr({bif,Line,M,F,As0}, Bs0, #ieval{level=Le}=Ieval0) ->
Ieval1 = Ieval0#ieval{line=Line},
{As,Bs} = eval_list(As0, Bs0, Ieval1),
trace(bif, {Le,Line,M,F,As}),
Ieval2 = dbg_istk:push(Bs0, Ieval1, false),
Ieval = Ieval2#ieval{module=M,function=F,arguments=As,line=-1},
{_,Value,_} = Res = debugged_cmd({apply,M,F,As}, Bs, Ieval),
trace(return, {Le,Value}),
dbg_istk:pop(),
Res;
%% Call to an operation
expr({op,Line,Op,As0}, Bs0, Ieval0) ->
Ieval = Ieval0#ieval{line=Line},
{As,Bs} = eval_list(As0, Bs0, Ieval),
try apply(erlang,Op,As) of
Value ->
{value,Value,Bs}
catch
Class:Reason ->
exception(Class, Reason, Bs, Ieval)
end;
%% apply/2 (fun)
expr({apply_fun,Line,Fun0,As0,Lc}, Bs0, #ieval{level=Le}=Ieval0) ->
Ieval = Ieval0#ieval{line=Line},
FunValue = case expr(Fun0, Bs0, Ieval) of
{value,{dbg_apply,Mx,Fx,Asx},Bsx} ->
debugged_cmd({apply,Mx,Fx,Asx},
Bsx, Ieval#ieval{level=Le+1});
OtherFunValue ->
OtherFunValue
end,
case FunValue of
{value,Fun,Bs1} when is_function(Fun) ->
{As,Bs} = eval_list(As0, Bs1, Ieval),
eval_function(undefined, Fun, As, Bs, extern, Ieval, Lc);
{value,{M,F},Bs1} when is_atom(M), is_atom(F) ->
{As,Bs} = eval_list(As0, Bs1, Ieval),
eval_function(M, F, As, Bs, extern, Ieval, Lc);
{value,BadFun,Bs1} ->
exception(error, {badfun,BadFun}, Bs1, Ieval)
end;
%% apply/3
expr({apply,Line,As0,Lc}, Bs0, Ieval0) ->
Ieval = Ieval0#ieval{line=Line},
{[M,F,As],Bs} = eval_list(As0, Bs0, Ieval),
eval_function(M, F, As, Bs, extern, Ieval, Lc);
%% Receive statement
expr({'receive',Line,Cs}, Bs0, #ieval{level=Le}=Ieval) ->
trace(receivex, {Le,false}),
eval_receive(get(self), Cs, Bs0, Ieval#ieval{line=Line});
%% Receive..after statement
expr({'receive',Line,Cs,To,ToExprs}, Bs0, #ieval{level=Le}=Ieval0) ->
Ieval = Ieval0#ieval{line=Line},
{value,ToVal,ToBs} = expr(To, Bs0, Ieval#ieval{top=false}),
trace(receivex, {Le,true}),
check_timeoutvalue(ToVal, ToBs, To, Ieval),
{Stamp,_} = statistics(wall_clock),
eval_receive(get(self), Cs, ToVal, ToExprs, ToBs, Bs0,
0, Stamp, Ieval);
%% Send (!)
expr({send,Line,To0,Msg0}, Bs0, Ieval0) ->
Ieval = Ieval0#ieval{line=Line},
Ieval1 = Ieval#ieval{top=false},
{value,To,Bs1} = expr(To0, Bs0, Ieval1),
{value,Msg,Bs2} = expr(Msg0, Bs0, Ieval1),
Bs = merge_bindings(Bs2, Bs1, Ieval),
eval_send(To, Msg, Bs, Ieval);
%% Binary
expr({bin,Line,Fs}, Bs0, Ieval0) ->
Ieval = Ieval0#ieval{line=Line,top=false},
try
eval_bits:expr_grp(Fs, Bs0,
fun (E, B) -> expr(E, B, Ieval) end,
[],
false)
catch
Class:Reason ->
exception(Class, Reason, Bs0, Ieval)
end;
%% List comprehension
expr({lc,_Line,E,Qs}, Bs, Ieval) ->
eval_lc(E, Qs, Bs, Ieval);
expr({bc,_Line,E,Qs}, Bs, Ieval) ->
eval_bc(E, Qs, Bs, Ieval);
%% Brutal exit on unknown expressions/clauses/values/etc.
expr(E, _Bs, _Ieval) ->
erlang:error({'NYI',E}).
%% Interpreted fun() called from uninterpreted module, recurse
eval_fun(As, {Info,Bs,Cs}) ->
dbg_debugged:eval(?MODULE, eval_fun, [Cs,As,Bs,Info]).
%% Interpreted named fun() called from uninterpreted module, recurse
eval_named_fun(As, RF, {Info,Bs,Cs,FName}) ->
dbg_debugged:eval(?MODULE, eval_named_fun, [Cs,As,Bs,FName,RF,Info]).
%% eval_lc(Expr,[Qualifier],Bindings,IevalState) ->
%% {value,Value,Bindings}.
%% This is evaluating list comprehensions "straight out of the book".
%% Copied from rv's implementation in erl_eval.
eval_lc(E, Qs, Bs, Ieval) ->
{value,eval_lc1(E, Qs, Bs, Ieval),Bs}.
eval_lc1(E, [{generate,Line,P,L0}|Qs], Bs0, Ieval0) ->
Ieval = Ieval0#ieval{line=Line},
{value,L1,Bs1} = expr(L0, Bs0, Ieval#ieval{top=false}),
CompFun = fun(NewBs) -> eval_lc1(E, Qs, NewBs, Ieval) end,
eval_generate(L1, P, Bs1, CompFun, Ieval);
eval_lc1(E, [{b_generate,Line,P,L0}|Qs], Bs0, Ieval0) ->
Ieval = Ieval0#ieval{line=Line},
{value,Bin,_} = expr(L0, Bs0, Ieval#ieval{top=false}),
CompFun = fun(NewBs) -> eval_lc1(E, Qs, NewBs, Ieval) end,
eval_b_generate(Bin, P, Bs0, CompFun, Ieval);
eval_lc1(E, [{guard,Q}|Qs], Bs0, Ieval) ->
case guard(Q, Bs0) of
true -> eval_lc1(E, Qs, Bs0, Ieval);
false -> []
end;
eval_lc1(E, [Q|Qs], Bs0, Ieval) ->
case expr(Q, Bs0, Ieval#ieval{top=false}) of
{value,true,Bs} -> eval_lc1(E, Qs, Bs, Ieval);
{value,false,_Bs} -> [];
{value,V,Bs} -> exception(error, {bad_filter,V}, Bs, Ieval)
end;
eval_lc1(E, [], Bs, Ieval) ->
{value,V,_} = expr(E, Bs, Ieval#ieval{top=false}),
[V].
%% eval_bc(Expr,[Qualifier],Bindings,IevalState) ->
%% {value,Value,Bindings}.
%% This is evaluating list comprehensions "straight out of the book".
%% Copied from rv's implementation in erl_eval.
eval_bc(E, Qs, Bs, Ieval) ->
Val = erlang:list_to_bitstring(eval_bc1(E, Qs, Bs, Ieval)),
{value,Val,Bs}.
eval_bc1(E, [{generate,Line,P,L0}|Qs], Bs0, Ieval0) ->
Ieval = Ieval0#ieval{line=Line},
{value,L1,Bs1} = expr(L0, Bs0, Ieval#ieval{top=false}),
CompFun = fun(NewBs) -> eval_bc1(E, Qs, NewBs, Ieval) end,
eval_generate(L1, P, Bs1, CompFun, Ieval);
eval_bc1(E, [{b_generate,Line,P,L0}|Qs], Bs0, Ieval0) ->
Ieval = Ieval0#ieval{line=Line},
{value,Bin,_} = expr(L0, Bs0, Ieval#ieval{top=false}),
CompFun = fun(NewBs) -> eval_bc1(E, Qs, NewBs, Ieval) end,
eval_b_generate(Bin, P, Bs0, CompFun, Ieval);
eval_bc1(E, [{guard,Q}|Qs], Bs0, Ieval) ->
case guard(Q, Bs0) of
true -> eval_bc1(E, Qs, Bs0, Ieval);
false -> []
end;
eval_bc1(E, [Q|Qs], Bs0, Ieval) ->
case expr(Q, Bs0, Ieval#ieval{top=false}) of
{value,true,Bs} -> eval_bc1(E, Qs, Bs, Ieval);
{value,false,_Bs} -> [];
{value,V,Bs} -> exception(error, {bad_filter,V}, Bs, Ieval)
end;
eval_bc1(E, [], Bs, Ieval) ->
{value,V,_} = expr(E, Bs, Ieval#ieval{top=false}),
[V].
eval_generate([V|Rest], P, Bs0, CompFun, Ieval) ->
case catch match1(P, V, erl_eval:new_bindings(), Bs0) of
{match,Bsn} ->
Bs2 = add_bindings(Bsn, Bs0),
CompFun(Bs2) ++ eval_generate(Rest, P, Bs0, CompFun, Ieval);
nomatch ->
eval_generate(Rest, P, Bs0, CompFun, Ieval)
end;
eval_generate([], _P, _Bs0, _CompFun, _Ieval) ->
[];
eval_generate(Term, _P, Bs, _CompFun, Ieval) ->
exception(error, {bad_generator,Term}, Bs, Ieval).
eval_b_generate(<<_/bitstring>>=Bin, P, Bs0, CompFun, Ieval) ->
Mfun = match_fun(Bs0),
Efun = fun(Exp, Bs) -> expr(Exp, Bs, #ieval{}) end,
case eval_bits:bin_gen(P, Bin, erl_eval:new_bindings(), Bs0, Mfun, Efun) of
{match,Rest,Bs1} ->
Bs2 = add_bindings(Bs1, Bs0),
CompFun(Bs2) ++ eval_b_generate(Rest, P, Bs0, CompFun, Ieval);
{nomatch,Rest} ->
eval_b_generate(Rest, P, Bs0, CompFun, Ieval);
done ->
[]
end;
eval_b_generate(Term, _P, Bs, _CompFun, Ieval) ->
exception(error, {bad_generator,Term}, Bs, Ieval).
safe_bif(M, F, As, Bs, Ieval) ->
try apply(M, F, As) of
Value ->
{value,Value,Bs}
catch
Class:Reason ->
exception(Class, Reason, Bs, Ieval, true)
end.
eval_send(To, Msg, Bs, Ieval) ->
try To ! Msg of
Msg ->
trace(send, {To,Msg}),
{value,Msg,Bs}
catch
Class:Reason ->
exception(Class, Reason, Bs, Ieval)
end.
%% Start tracing of messages before fetching current messages in
%% the queue to make sure that no messages are lost.
eval_receive(Debugged, Cs, Bs0,
#ieval{module=M,line=Line,level=Le}=Ieval) ->
%% To avoid private message passing protocol between META
%% and interpreted process.
erlang:trace(Debugged,true,['receive']),
{_,Msgs} = erlang:process_info(Debugged,messages),
case receive_clauses(Cs, Bs0, Msgs) of
nomatch ->
dbg_iserver:cast(get(int), {set_status, self(),waiting,{}}),
dbg_icmd:tell_attached({wait_at,M,Line,Le}),
eval_receive1(Debugged, Cs, Bs0, Ieval);
{eval,B,Bs,Msg} ->
rec_mess(Debugged, Msg, Bs, Ieval),
seq(B, Bs, Ieval)
end.
eval_receive1(Debugged, Cs, Bs0, Ieval) ->
Msgs = do_receive(Debugged, Bs0, Ieval),
case receive_clauses(Cs, Bs0, Msgs) of
nomatch ->
eval_receive1(Debugged, Cs, Bs0, Ieval);
{eval,B,Bs,Msg} ->
rec_mess(Debugged, Msg, Bs0, Ieval),
dbg_iserver:cast(get(int), {set_status, self(),running,{}}),
dbg_icmd:tell_attached(running),
seq(B, Bs, Ieval)
end.
check_timeoutvalue(ToVal,_,_,_) when is_integer(ToVal), ToVal>=0 -> true;
check_timeoutvalue(infinity,_,_,_) -> true;
check_timeoutvalue(_ToVal, ToBs, To, Ieval) ->
Line = element(2, To),
exception(error, timeout_value, ToBs, Ieval#ieval{line=Line}).
eval_receive(Debugged, Cs, 0, ToExprs, ToBs, Bs0, 0, _Stamp, Ieval) ->
{_,Msgs} = erlang:process_info(Debugged,messages),
case receive_clauses(Cs, Bs0, Msgs) of
nomatch ->
trace(received,null),
seq(ToExprs, ToBs, Ieval);
{eval,B,Bs,Msg} ->
rec_mess_no_trace(Debugged, Msg, Bs0, Ieval),
seq(B, Bs, Ieval)
end;
eval_receive(Debugged, Cs, ToVal, ToExprs, ToBs, Bs0,
0, Stamp, #ieval{module=M,line=Line,level=Le}=Ieval)->
erlang:trace(Debugged,true,['receive']),
{_,Msgs} = erlang:process_info(Debugged,messages),
case receive_clauses(Cs, Bs0, Msgs) of
nomatch ->
{Stamp1,Time1} = newtime(Stamp,ToVal),
dbg_iserver:cast(get(int), {set_status, self(),waiting,{}}),
dbg_icmd:tell_attached({wait_after_at,M,Line,Le}),
eval_receive(Debugged, Cs, Time1, ToExprs, ToBs, Bs0,
infinity,Stamp1, Ieval);
{eval,B,Bs,Msg} ->
rec_mess(Debugged, Msg, Bs0, Ieval),
seq(B, Bs, Ieval)
end;
eval_receive(Debugged, Cs, ToVal, ToExprs, ToBs, Bs0,
_, Stamp, Ieval) ->
case do_receive(Debugged, ToVal, Stamp, Bs0, Ieval) of
timeout ->
trace(received,null),
rec_mess(Debugged),
dbg_iserver:cast(get(int), {set_status, self(),running,{}}),
dbg_icmd:tell_attached(running),
seq(ToExprs, ToBs, Ieval);
Msgs ->
case receive_clauses(Cs, Bs0, Msgs) of
nomatch ->
{Stamp1,Time1} = newtime(Stamp,ToVal),
eval_receive(Debugged, Cs, Time1, ToExprs, ToBs,
Bs0, infinity,Stamp1, Ieval);
{eval,B,Bs,Msg} ->
rec_mess(Debugged, Msg, Bs0, Ieval),
dbg_iserver:cast(get(int),
{set_status, self(), running, {}}),
dbg_icmd:tell_attached(running),
seq(B, Bs, Ieval)
end
end.
do_receive(Debugged, Bs, Ieval) ->
receive
{trace,Debugged,'receive',Msg} ->
[Msg];
Msg ->
check_exit_msg(Msg, Bs, Ieval),
dbg_icmd:handle_msg(Msg, wait_at, Bs, Ieval),
do_receive(Debugged, Bs, Ieval)
end.
do_receive(Debugged, Time, Stamp, Bs, Ieval) ->
receive
{trace,Debugged,'receive',Msg} ->
[Msg];
{user, timeout} ->
timeout;
Msg ->
check_exit_msg(Msg, Bs, Ieval),
dbg_icmd:handle_msg(Msg, wait_after_at, Bs, Ieval),
{Stamp1,Time1} = newtime(Stamp,Time),
do_receive(Debugged, Time1, Stamp1, Bs, Ieval)
after Time ->
timeout
end.
newtime(Stamp,infinity) ->
{Stamp,infinity};
newtime(Stamp,Time) ->
{Stamp1,_} = statistics(wall_clock),
case Time - (Stamp1 - Stamp) of
NewTime when NewTime > 0 ->
{Stamp1,NewTime};
_ ->
{Stamp1,0}
end.
rec_mess(Debugged, Msg, Bs, Ieval) ->
erlang:trace(Debugged, false, ['receive']),
flush_traces(Debugged),
Debugged ! {sys,self(),{'receive',Msg}},
rec_ack(Debugged, Bs, Ieval).
rec_mess(Debugged) ->
erlang:trace(Debugged, false, ['receive']),
flush_traces(Debugged).
rec_mess_no_trace(Debugged, Msg, Bs, Ieval) ->
Debugged ! {sys,self(),{'receive',Msg}},
rec_ack(Debugged, Bs, Ieval).
rec_ack(Debugged, Bs, Ieval) ->
receive
{Debugged,rec_acked} ->
true;
Msg ->
check_exit_msg(Msg, Bs, Ieval),
io:format("***WARNING*** Unexp msg ~p, ieval ~p~n",
[Msg, Ieval])
end.
flush_traces(Debugged) ->
receive
{trace,Debugged,'receive',_} ->
flush_traces(Debugged)
after 0 ->
true
end.
%% eval_list(ExpressionList, Bindings, Ieval)
%% Evaluate a list of expressions "in parallel" at the same level.
eval_list(Es, Bs, Ieval) ->
eval_list_1(Es, [], Bs, Bs, Ieval#ieval{top=false}).
eval_list_1([E|Es], Vs, BsOrig, Bs0, Ieval) ->
{value,V,Bs1} = expr(E, BsOrig, Ieval),
eval_list_1(Es, [V|Vs], BsOrig, merge_bindings(Bs1, Bs0, Ieval), Ieval);
eval_list_1([], Vs, _, Bs, _Ieval) ->
{lists:reverse(Vs,[]),Bs}.
%% if_clauses(Clauses, Bindings, Ieval)
if_clauses([{clause,_,[],G,B}|Cs], Bs, Ieval) ->
case guard(G, Bs) of
true ->
seq(B, Bs, Ieval);
false ->
if_clauses(Cs, Bs, Ieval)
end;
if_clauses([], Bs, Ieval) ->
exception(error, if_clause, Bs, Ieval).
%% case_clauses(Value, Clauses, Bindings, Error, Ieval)
%% Error = try_clause | case_clause
case_clauses(Val, [{clause,_,[P],G,B}|Cs], Bs0, Error, Ieval) ->
case match(P, Val, Bs0) of
{match,Bs} ->
case guard(G, Bs) of
true ->
seq(B, Bs, Ieval);
false ->
case_clauses(Val, Cs, Bs0, Error, Ieval)
end;
nomatch ->
case_clauses(Val, Cs, Bs0, Error, Ieval)
end;
case_clauses(Val,[], Bs, Error, Ieval) ->
exception(error, {Error,Val}, Bs, Ieval).
%% catch_clauses(Exception, Clauses, Bindings, Ieval)
%% Exception = {Class,Reason,[]}
catch_clauses(Exception, [{clause,_,[P],G,B}|CatchCs], Bs0, Ieval) ->
case match(P, Exception, Bs0) of
{match,Bs} ->
case guard(G, Bs) of
true ->
%% Exception caught, reset exit info
put(exit_info, undefined),
dbg_istk:pop(Ieval#ieval.level),
seq(B, Bs, Ieval);
false ->
catch_clauses(Exception, CatchCs, Bs0, Ieval)
end;
nomatch ->
catch_clauses(Exception, CatchCs, Bs0, Ieval)
end;
catch_clauses({Class,Reason,[]}, [], _Bs, _Ieval) ->
erlang:Class(Reason).
receive_clauses(Cs, Bs0, [Msg|Msgs]) ->
case rec_clauses(Cs, Bs0, Msg) of
nomatch ->
receive_clauses(Cs, Bs0, Msgs);
{eval,B,Bs} ->
{eval,B,Bs,Msg}
end;
receive_clauses(_, _, []) ->
nomatch.
rec_clauses([{clause,_,Pars,G,B}|Cs], Bs0, Msg) ->
case rec_match(Pars, Msg, Bs0) of
{match,Bs} ->
case guard(G, Bs) of
true ->
trace(received, Msg),
{eval,B,Bs};
false ->
rec_clauses(Cs, Bs0, Msg)
end;
nomatch ->
rec_clauses(Cs, Bs0, Msg)
end;
rec_clauses([], _, _) ->
nomatch.
%% guard(GuardTests,Bindings)
%% Evaluate a list of guards.
guard([], _) -> true;
guard(Gs, Bs) -> or_guard(Gs, Bs).
or_guard([G|Gs], Bs) ->
%% Short-circuit OR.
and_guard(G, Bs) orelse or_guard(Gs, Bs);
or_guard([], _) -> false.
and_guard([G|Gs], Bs) ->
%% Short-circuit AND.
case catch guard_expr(G, Bs) of
{value,true} -> and_guard(Gs, Bs);
_ -> false
end;
and_guard([],_) -> true.
guard_exprs([A0|As0], Bs) ->
{value,A} = guard_expr(A0, Bs),
{values,As} = guard_exprs(As0, Bs),
{values,[A|As]};
guard_exprs([], _) ->
{values,[]}.
guard_expr({'andalso',_,E1,E2}, Bs) ->
case guard_expr(E1, Bs) of
{value,false}=Res -> Res;
{value,true} ->
case guard_expr(E2, Bs) of
{value,_Val}=Res -> Res
end
end;
guard_expr({'orelse',_,E1,E2}, Bs) ->
case guard_expr(E1, Bs) of
{value,true}=Res -> Res;
{value,false} ->
case guard_expr(E2, Bs) of
{value,_Val}=Res -> Res
end
end;
guard_expr({dbg,_,self,[]}, _) ->
{value,get(self)};
guard_expr({safe_bif,_,erlang,'not',As0}, Bs) ->
{values,As} = guard_exprs(As0, Bs),
{value,apply(erlang, 'not', As)};
guard_expr({safe_bif,_,Mod,Func,As0}, Bs) ->
{values,As} = guard_exprs(As0, Bs),
{value,apply(Mod, Func, As)};
guard_expr({var,_,V}, Bs) ->
{value,_} = binding(V, Bs);
guard_expr({value,_,Val}, _Bs) ->
{value,Val};
guard_expr({cons,_,H0,T0}, Bs) ->
{value,H} = guard_expr(H0, Bs),
{value,T} = guard_expr(T0, Bs),
{value,[H|T]};
guard_expr({tuple,_,Es0}, Bs) ->
{values,Es} = guard_exprs(Es0, Bs),
{value,list_to_tuple(Es)};
guard_expr({map,_,Fs}, Bs0) ->
F = fun (G0, B0, _) ->
{value,G} = guard_expr(G0, B0),
{value,G,B0}
end,
{Map,_} = eval_new_map_fields(Fs, Bs0, #ieval{top=false}, F),
{value,Map};
guard_expr({map,_,E0,Fs0}, Bs) ->
{value,E} = guard_expr(E0, Bs),
Fs = eval_map_fields_guard(Fs0, Bs),
Value = lists:foldl(fun ({map_assoc,K,V}, Mi) -> maps:put(K,V,Mi);
({map_exact,K,V}, Mi) -> maps:update(K,V,Mi) end,
E, Fs),
{value,Value};
guard_expr({bin,_,Flds}, Bs) ->
{value,V,_Bs} =
eval_bits:expr_grp(Flds, Bs,
fun(E,B) ->
{value,V} = guard_expr(E,B),
{value,V,B}
end, [], false),
{value,V}.
%% eval_map_fields([Field], Bindings, IEvalState) ->
%% {[{map_assoc | map_exact,Key,Value}],Bindings}
eval_map_fields(Fs, Bs, Ieval) ->
eval_map_fields(Fs, Bs, Ieval, fun expr/3).
eval_map_fields_guard(Fs0, Bs) ->
{Fs,_} = eval_map_fields(Fs0, Bs, #ieval{},
fun (G0, Bs0, _) ->
{value,G} = guard_expr(G0, Bs0),
{value,G,Bs0}
end),
Fs.
eval_map_fields(Fs, Bs, Ieval, F) ->
eval_map_fields(Fs, Bs, Ieval, F, []).
eval_map_fields([{map_field_assoc,Line,K0,V0}|Fs], Bs0, Ieval0, F, Acc) ->
Ieval = Ieval0#ieval{line=Line},
{value,K,Bs1} = F(K0, Bs0, Ieval),
{value,V,Bs2} = F(V0, Bs1, Ieval),
eval_map_fields(Fs, Bs2, Ieval0, F, [{map_assoc,K,V}|Acc]);
eval_map_fields([{map_field_exact,Line,K0,V0}|Fs], Bs0, Ieval0, F, Acc) ->
Ieval = Ieval0#ieval{line=Line},
{value,K,Bs1} = F(K0, Bs0, Ieval),
{value,V,Bs2} = F(V0, Bs1, Ieval),
eval_map_fields(Fs, Bs2, Ieval0, F, [{map_exact,K,V}|Acc]);
eval_map_fields([], Bs, _Ieval, _F, Acc) ->
{lists:reverse(Acc),Bs}.
eval_new_map_fields(Fs, Bs0, Ieval, F) ->
eval_new_map_fields(Fs, Bs0, Ieval, F, []).
eval_new_map_fields([{Line,K0,V0}|Fs], Bs0, Ieval0, F, Acc) ->
Ieval = Ieval0#ieval{line=Line},
{value,K,Bs1} = F(K0, Bs0, Ieval),
{value,V,Bs2} = F(V0, Bs1, Ieval),
eval_new_map_fields(Fs, Bs2, Ieval0, F, [{K,V}|Acc]);
eval_new_map_fields([], Bs, _, _, Acc) ->
{maps:from_list(lists:reverse(Acc)),Bs}.
%% match(Pattern,Term,Bs) -> {match,Bs} | nomatch
match(Pat, Term, Bs) ->
try match1(Pat, Term, Bs, Bs)
catch
Result -> Result
end.
match1({value,_,V}, V, Bs,_BBs) ->
{match,Bs};
match1({var,_,'_'}, Term, Bs,_BBs) -> % Anonymous variable matches
{match,add_anon(Term, Bs)}; % everything,save it anyway
match1({var,_,Name}, Term, Bs, _BBs) ->
case binding(Name, Bs) of
{value,Term} ->
{match,Bs};
{value,_} ->
throw(nomatch);
unbound ->
{match,[{Name,Term}|Bs]} % Add the new binding
end;
match1({match,_,Pat1,Pat2}, Term, Bs0, BBs) ->
{match,Bs1} = match1(Pat1, Term, Bs0, BBs),
match1(Pat2, Term, Bs1, BBs);
match1({cons,_,H,T}, [H1|T1], Bs0, BBs) ->
{match,Bs} = match1(H, H1, Bs0, BBs),
match1(T, T1, Bs, BBs);
match1({tuple,_,Elts}, Tuple, Bs, BBs)
when length(Elts) =:= tuple_size(Tuple) ->
match_tuple(Elts, Tuple, 1, Bs, BBs);
match1({map,_,Fields}, Map, Bs, BBs) when is_map(Map) ->
match_map(Fields, Map, Bs, BBs);
match1({bin,_,Fs}, B, Bs0, BBs) when is_bitstring(B) ->
try eval_bits:match_bits(Fs, B, Bs0, BBs,
match_fun(BBs),
fun(E, Bs) -> expr(E, Bs, #ieval{}) end,
false)
catch
_:_ -> throw(nomatch)
end;
match1(_,_,_,_) ->
throw(nomatch).
match_fun(BBs) ->
fun(match, {L,R,Bs}) -> match1(L, R, Bs, BBs);
(binding, {Name,Bs}) -> binding(Name, Bs);
(add_binding, {Name,Val,Bs}) -> add_binding(Name, Val, Bs)
end.
match_tuple([E|Es], Tuple, I, Bs0, BBs) ->
{match,Bs} = match1(E, element(I, Tuple), Bs0, BBs),
match_tuple(Es, Tuple, I+1, Bs, BBs);
match_tuple([], _, _, Bs, _BBs) ->
{match,Bs}.
match_map([{map_field_exact,_,K0,Pat}|Fs], Map, Bs0, BBs) ->
{value,K,BBs} = expr(K0, BBs, #ieval{}),
case maps:find(K, Map) of
{ok,Value} ->
{match,Bs} = match1(Pat, Value, Bs0, BBs),
match_map(Fs, Map, Bs, BBs);
error -> throw(nomatch)
end;
match_map([], _, Bs, _BBs) ->
{match,Bs}.
head_match([Par|Pars], [Arg|Args], Bs0, BBs) ->
try match1(Par, Arg, Bs0, BBs) of
{match,Bs} -> head_match(Pars, Args, Bs, BBs)
catch
Result -> Result
end;
head_match([],[],Bs,_) -> {match,Bs}.
rec_match([Par],Msg,Bs0) ->
match(Par,Msg,Bs0).
binding(Name,[{Name,Val}|_]) ->
{value,Val};
binding(Name,[_,{Name,Val}|_]) ->
{value,Val};
binding(Name,[_,_,{Name,Val}|_]) ->
{value,Val};
binding(Name,[_,_,_,{Name,Val}|_]) ->
{value,Val};
binding(Name,[_,_,_,_,{Name,Val}|_]) ->
{value,Val};
binding(Name,[_,_,_,_,_,{Name,Val}|_]) ->
{value,Val};
binding(Name,[_,_,_,_,_,_|Bs]) ->
binding(Name,Bs);
binding(Name,[_,_,_,_,_|Bs]) ->
binding(Name,Bs);
binding(Name,[_,_,_,_|Bs]) ->
binding(Name,Bs);
binding(Name,[_,_,_|Bs]) ->
binding(Name,Bs);
binding(Name,[_,_|Bs]) ->
binding(Name,Bs);
binding(Name,[_|Bs]) ->
binding(Name,Bs);
binding(_,[]) ->
unbound.
add_anon(Val,[{'_',_}|Bs]) ->
[{'_',Val}|Bs];
add_anon(Val,[B1,{'_',_}|Bs]) ->
[B1,{'_',Val}|Bs];
add_anon(Val,[B1,B2,{'_',_}|Bs]) ->
[B1,B2,{'_',Val}|Bs];
add_anon(Val,[B1,B2,B3,{'_',_}|Bs]) ->
[B1,B2,B3,{'_',Val}|Bs];
add_anon(Val,[B1,B2,B3,B4,{'_',_}|Bs]) ->
[B1,B2,B3,B4,{'_',Val}|Bs];
add_anon(Val,[B1,B2,B3,B4,B5,{'_',_}|Bs]) ->
[B1,B2,B3,B4,B5,{'_',Val}|Bs];
add_anon(Val,[B1,B2,B3,B4,B5,B6|Bs]) ->
[B1,B2,B3,B4,B5,B6|add_anon(Val,Bs)];
add_anon(Val,[B1,B2,B3,B4,B5|Bs]) ->
[B1,B2,B3,B4,B5|add_anon(Val,Bs)];
add_anon(Val,[B1,B2,B3,B4|Bs]) ->
[B1,B2,B3,B4|add_anon(Val,Bs)];
add_anon(Val,[B1,B2,B3|Bs]) ->
[B1,B2,B3|add_anon(Val,Bs)];
add_anon(Val,[B1,B2|Bs]) ->
[B1,B2|add_anon(Val,Bs)];
add_anon(Val,[B1|Bs]) ->
[B1|add_anon(Val,Bs)];
add_anon(Val,[]) ->
[{'_',Val}].
%% merge_bindings(Bindings1, Bindings2, Ieval)
%% Merge bindings detecting bad matches.
%% Special case '_',save the new one !!!
%% Bindings1 is the newest bindings.
merge_bindings(Bs, Bs, _Ieval) ->
Bs; % Identical bindings
merge_bindings([{Name,V}|B1s], B2s, Ieval) ->
case binding(Name, B2s) of
{value,V} -> % Already there, and the same
merge_bindings(B1s, B2s, Ieval);
{value,_} when Name =:= '_' -> % Already there, but anonymous
B2s1 = lists:keydelete('_', 1, B2s),
[{Name,V}|merge_bindings(B1s, B2s1, Ieval)];
{value,_} -> % Already there, but different => badmatch
exception(error, {badmatch,V}, B2s, Ieval);
unbound -> % Not there,add it
[{Name,V}|merge_bindings(B1s, B2s, Ieval)]
end;
merge_bindings([], B2s, _Ieval) ->
B2s.
%% add_bindings(Bindings1,Bindings2)
%% Add Bindings1 to Bindings2. Bindings in
%% Bindings1 hides bindings in Bindings2.
%% Used in list comprehensions (and funs).
add_bindings(Bs1,[]) ->
Bs1;
add_bindings([{Name,V}|Bs],ToBs0) ->
ToBs = add_binding(Name,V,ToBs0),
add_bindings(Bs,ToBs);
add_bindings([],ToBs) ->
ToBs.
add_binding(N,Val,[{N,_}|Bs]) ->
[{N,Val}|Bs];
add_binding(N,Val,[B1,{N,_}|Bs]) ->
[B1,{N,Val}|Bs];
add_binding(N,Val,[B1,B2,{N,_}|Bs]) ->
[B1,B2,{N,Val}|Bs];
add_binding(N,Val,[B1,B2,B3,{N,_}|Bs]) ->
[B1,B2,B3,{N,Val}|Bs];
add_binding(N,Val,[B1,B2,B3,B4,{N,_}|Bs]) ->
[B1,B2,B3,B4,{N,Val}|Bs];
add_binding(N,Val,[B1,B2,B3,B4,B5,{N,_}|Bs]) ->
[B1,B2,B3,B4,B5,{N,Val}|Bs];
add_binding(N,Val,[B1,B2,B3,B4,B5,B6|Bs]) ->
[B1,B2,B3,B4,B5,B6|add_binding(N,Val,Bs)];
add_binding(N,Val,[B1,B2,B3,B4,B5|Bs]) ->
[B1,B2,B3,B4,B5|add_binding(N,Val,Bs)];
add_binding(N,Val,[B1,B2,B3,B4|Bs]) ->
[B1,B2,B3,B4|add_binding(N,Val,Bs)];
add_binding(N,Val,[B1,B2,B3|Bs]) ->
[B1,B2,B3|add_binding(N,Val,Bs)];
add_binding(N,Val,[B1,B2|Bs]) ->
[B1,B2|add_binding(N,Val,Bs)];
add_binding(N,Val,[B1|Bs]) ->
[B1|add_binding(N,Val,Bs)];
add_binding(N,Val,[]) ->
[{N,Val}].
%% get_stacktrace() -> Stacktrace
%% Return the latest stacktrace for the process.
get_stacktrace() ->
case get(stacktrace) of
MakeStk when is_function(MakeStk, 1) ->
%% The stacktrace has not been constructed before.
%% Construct it and remember the result.
Depth = erlang:system_flag(backtrace_depth, 8),
erlang:system_flag(backtrace_depth, Depth),
Stk = MakeStk(Depth),
put(stacktrace, Stk),
Stk;
Stk when is_list(Stk) ->
Stk
end.