Files
mercury/browser/declarative_debugger.m
Mark Brown 05acf10572 Fix a bug in term dependencies in the declarative debugger.
Estimated hours taken: 5

Fix a bug in term dependencies in the declarative debugger.  The code
is not currently used, but is required for a change that I am working
on at the moment.  This will be committed on the main branch only.

browser/declarative_debugger.m:
	Make sure the list of atomic goal representations is built in
	mode-correct order.
2001-01-24 05:18:02 +00:00

1101 lines
33 KiB
Mathematica

%-----------------------------------------------------------------------------%
% Copyright (C) 1999-2001 The University of Melbourne.
% This file may only be copied under the terms of the GNU Library General
% Public License - see the file COPYING.LIB in the Mercury distribution.
%-----------------------------------------------------------------------------%
% File: declarative_debugger.m
% Author: Mark Brown
%
% This module has two main purposes:
% - to define the interface between the front and back ends of
% a Mercury declarative debugger, and
% - to implement a front end.
%
% The interface is defined by a procedure that can be called from
% the back end to perform diagnosis, and a typeclass which represents
% a declarative view of execution used by the front end.
%
% The front end implemented in this module analyses the EDT it is
% passed to diagnose a bug. It does this by a simple top-down search.
%
:- module mdb__declarative_debugger.
:- interface.
:- import_module io, list, bool, std_util.
:- import_module mdb__declarative_execution.
% This type represents the possible truth values for nodes
% in the EDT.
%
:- type decl_truth == bool.
% This type represents the possible responses to being
% asked to confirm that a node is a bug.
%
:- type decl_confirmation
---> confirm_bug
; overrule_bug
; abort_diagnosis.
% This type represents the bugs which can be diagnosed.
% The parameter of the constructor is the type of EDT nodes.
%
:- type decl_bug
% An EDT whose root node is incorrect,
% but whose children are all correct.
%
---> e_bug(decl_e_bug)
% An EDT whose root node is incorrect, and
% which has no incorrect children but at
% least one inadmissible one.
%
; i_bug(decl_i_bug).
:- type decl_e_bug
---> incorrect_contour(
decl_atom, % The head of the clause, in its
% final state of instantiation.
decl_contour, % The path taken through the body.
event_number % The exit event.
)
; partially_uncovered_atom(
decl_atom, % The called atom, in its initial
% state.
event_number % The fail event.
)
; unhandled_exception(
decl_atom, % The called atom, in its initial
% state.
decl_exception, % The exception thrown.
event_number % The excp event.
).
:- type decl_i_bug
---> inadmissible_call(
decl_atom, % The parent atom, in its initial
% state.
decl_position, % The location of the call in the
% parent's body.
decl_atom, % The inadmissible child, in its
% initial state.
event_number % The call event.
).
% XXX not yet implemented.
%
:- type decl_contour == unit.
:- type decl_position == unit.
% Values of this type represent goal behaviour. This representation
% is used by the front end (in this module), as well as the
% oracle and user interface.
%
:- type decl_question
% The node is a suspected wrong answer. The
% argument is the atom in its final state of
% instantiatedness (ie. at the EXIT event).
%
---> wrong_answer(decl_atom)
% The node is a suspected missing answer. The
% first argument is the atom in its initial state
% of instantiatedness (ie. at the CALL event),
% and the second argument is the list of solutions.
%
; missing_answer(decl_atom, list(decl_atom))
% The node is a possibly unexpected exception.
% The first argument is the atom in its initial
% state of instantiation, and the second argument
% is the exception thrown.
%
; unexpected_exception(decl_atom, decl_exception).
:- type decl_answer == pair(decl_question, decl_truth).
:- type decl_atom == trace_atom.
:- type decl_exception == univ.
% The diagnoser eventually responds with a value of this type
% after it is called.
%
:- type diagnoser_response
% There was a bug found and confirmed. The
% event number is for a call port (inadmissible
% call), an exit port (incorrect contour),
% or a fail port (partially uncovered atom).
%
---> bug_found(event_number)
% There was no symptom found, or the diagnoser
% aborted before finding a bug.
%
; no_bug_found
% The analyser requires the back end to reproduce
% part of the annotated trace, with a greater
% depth bound. The event number and sequence
% number are for the final event required (the
% first event required is the call event with
% the same sequence number).
%
; require_subtree(event_number, sequence_number).
:- type diagnoser_state(R).
:- pred diagnoser_state_init(io__input_stream, io__output_stream,
diagnoser_state(R)).
:- mode diagnoser_state_init(in, in, out) is det.
:- pred diagnosis(S, R, diagnoser_response, diagnoser_state(R),
diagnoser_state(R), io__state, io__state)
<= annotated_trace(S, R).
:- mode diagnosis(in, in, out, in, out, di, uo) is det.
%-----------------------------------------------------------------------------%
:- implementation.
:- import_module require, int, char, string.
:- import_module mdb__declarative_analyser, mdb__declarative_oracle.
:- import_module mdb__program_representation.
:- type diagnoser_state(R)
---> diagnoser(
analyser_state(edt_node(R)),
oracle_state
).
:- pred diagnoser_get_analyser(diagnoser_state(R),
analyser_state(edt_node(R))).
:- mode diagnoser_get_analyser(in, out) is det.
diagnoser_get_analyser(diagnoser(Analyser, _), Analyser).
:- pred diagnoser_set_analyser(diagnoser_state(R), analyser_state(edt_node(R)),
diagnoser_state(R)).
:- mode diagnoser_set_analyser(in, in, out) is det.
diagnoser_set_analyser(diagnoser(_, B), A, diagnoser(A, B)).
:- pred diagnoser_get_oracle(diagnoser_state(R), oracle_state).
:- mode diagnoser_get_oracle(in, out) is det.
diagnoser_get_oracle(diagnoser(_, Oracle), Oracle).
:- pred diagnoser_set_oracle(diagnoser_state(R), oracle_state,
diagnoser_state(R)).
:- mode diagnoser_set_oracle(in, in, out) is det.
diagnoser_set_oracle(diagnoser(A, _), B, diagnoser(A, B)).
diagnoser_state_init(InStr, OutStr, Diagnoser) :-
analyser_state_init(Analyser),
oracle_state_init(InStr, OutStr, Oracle),
Diagnoser = diagnoser(Analyser, Oracle).
diagnosis(Store, NodeId, Response, Diagnoser0, Diagnoser) -->
{ diagnoser_get_analyser(Diagnoser0, Analyser0) },
{ start_analysis(wrap(Store), dynamic(NodeId), AnalyserResponse,
Analyser0, Analyser) },
{ diagnoser_set_analyser(Diagnoser0, Analyser, Diagnoser1) },
handle_analyser_response(Store, AnalyserResponse, Response,
Diagnoser1, Diagnoser).
:- pred handle_analyser_response(S, analyser_response(edt_node(R)),
diagnoser_response, diagnoser_state(R), diagnoser_state(R),
io__state, io__state) <= annotated_trace(S, R).
:- mode handle_analyser_response(in, in, out, in, out, di, uo) is det.
handle_analyser_response(_, no_suspects, no_bug_found, D, D) -->
io__write_string("No bug found.\n").
handle_analyser_response(_, bug_found(Bug), Response, Diagnoser0,
Diagnoser) -->
confirm_bug(Bug, Response, Diagnoser0, Diagnoser).
handle_analyser_response(Store, oracle_queries(Queries), Response,
Diagnoser0, Diagnoser) -->
{ diagnoser_get_oracle(Diagnoser0, Oracle0) },
query_oracle(Queries, OracleResponse, Oracle0, Oracle),
{ diagnoser_set_oracle(Diagnoser0, Oracle, Diagnoser1) },
handle_oracle_response(Store, OracleResponse, Response, Diagnoser1,
Diagnoser).
handle_analyser_response(Store, require_explicit(Tree), Response,
Diagnoser, Diagnoser) -->
{
edt_subtree_details(Store, Tree, Event, Seqno),
Response = require_subtree(Event, Seqno)
}.
:- pred handle_oracle_response(S, oracle_response, diagnoser_response,
diagnoser_state(R), diagnoser_state(R), io__state, io__state)
<= annotated_trace(S, R).
:- mode handle_oracle_response(in, in, out, in, out, di, uo) is det.
handle_oracle_response(Store, oracle_answers(Answers), Response, Diagnoser0,
Diagnoser) -->
{ diagnoser_get_analyser(Diagnoser0, Analyser0) },
{ continue_analysis(wrap(Store), Answers, AnalyserResponse,
Analyser0, Analyser) },
{ diagnoser_set_analyser(Diagnoser0, Analyser, Diagnoser1) },
handle_analyser_response(Store, AnalyserResponse, Response,
Diagnoser1, Diagnoser).
handle_oracle_response(_, no_oracle_answers, no_bug_found, D, D) -->
[].
handle_oracle_response(_, abort_diagnosis, no_bug_found, D, D) -->
io__write_string("Diagnosis aborted.\n").
:- pred confirm_bug(decl_bug, diagnoser_response, diagnoser_state(R),
diagnoser_state(R), io__state, io__state).
:- mode confirm_bug(in, out, in, out, di, uo) is det.
confirm_bug(Bug, Response, Diagnoser0, Diagnoser) -->
{ diagnoser_get_oracle(Diagnoser0, Oracle0) },
oracle_confirm_bug(Bug, Confirmation, Oracle0, Oracle),
{ diagnoser_set_oracle(Diagnoser0, Oracle, Diagnoser) },
{
Confirmation = confirm_bug,
decl_bug_get_event_number(Bug, Event),
Response = bug_found(Event)
;
Confirmation = overrule_bug,
Response = no_bug_found
;
Confirmation = abort_diagnosis,
Response = no_bug_found
}.
% Export a monomorphic version of diagnosis_state_init/4, to
% make it easier to call from C code.
%
:- pred diagnoser_state_init_store(io__input_stream, io__output_stream,
diagnoser_state(trace_node_id)).
:- mode diagnoser_state_init_store(in, in, out) is det.
:- pragma export(diagnoser_state_init_store(in, in, out),
"MR_DD_decl_diagnosis_state_init").
diagnoser_state_init_store(InStr, OutStr, Diagnoser) :-
diagnoser_state_init(InStr, OutStr, Diagnoser).
% Export a monomorphic version of diagnosis/9, to make it
% easier to call from C code.
%
:- pred diagnosis_store(trace_node_store, trace_node_id, diagnoser_response,
diagnoser_state(trace_node_id), diagnoser_state(trace_node_id),
io__state, io__state).
:- mode diagnosis_store(in, in, out, in, out, di, uo) is det.
:- pragma export(diagnosis_store(in, in, out, in, out, di, uo),
"MR_DD_decl_diagnosis").
diagnosis_store(Store, Node, Response, State0, State) -->
diagnosis(Store, Node, Response, State0, State).
% Export some predicates so that C code can interpret the
% diagnoser response.
%
:- pred diagnoser_bug_found(diagnoser_response, event_number).
:- mode diagnoser_bug_found(in, out) is semidet.
:- pragma export(diagnoser_bug_found(in, out), "MR_DD_diagnoser_bug_found").
diagnoser_bug_found(bug_found(Event), Event).
:- pred diagnoser_require_subtree(diagnoser_response, event_number,
sequence_number).
:- mode diagnoser_require_subtree(in, out, out) is semidet.
:- pragma export(diagnoser_require_subtree(in, out, out),
"MR_DD_diagnoser_require_subtree").
diagnoser_require_subtree(require_subtree(Event, SeqNo), Event, SeqNo).
%-----------------------------------------------------------------------------%
%
% This section defines an instance of the EDT in terms of
% any instance of execution tree.
%
:- type edt_node(R)
---> dynamic(R).
:- instance mercury_edt(wrap(S), edt_node(R)) <= annotated_trace(S, R)
where [
pred(edt_root_question/3) is trace_root_question,
pred(edt_root_e_bug/3) is trace_root_e_bug,
pred(edt_children/3) is trace_children,
pred(edt_dependency/6) is trace_dependency
].
% The wrap/1 around the first argument of the instance is
% required by the language.
%
:- type wrap(S) ---> wrap(S).
:- pred trace_root_question(wrap(S), edt_node(R), decl_question)
<= annotated_trace(S, R).
:- mode trace_root_question(in, in, out) is det.
trace_root_question(wrap(Store), dynamic(Ref), Root) :-
det_edt_node_from_id(Store, Ref, Node),
(
Node = fail(_, CallId, RedoId, _),
call_node_from_id(Store, CallId, Call),
Call = call(_, _, CallAtom, _, _, _, _),
get_answers(Store, RedoId, [], Answers),
Root = missing_answer(CallAtom, Answers)
;
Node = exit(_, _, _, ExitAtom, _),
Root = wrong_answer(ExitAtom)
;
Node = excp(_, CallId, _, Exception, _),
call_node_from_id(Store, CallId, Call),
Call = call(_, _, CallAtom, _, _, _, _),
Root = unexpected_exception(CallAtom, Exception)
).
:- pred get_answers(S, R, list(decl_atom), list(decl_atom))
<= annotated_trace(S, R).
:- mode get_answers(in, in, in, out) is det.
get_answers(Store, RedoId, As0, As) :-
(
maybe_redo_node_from_id(Store, RedoId, redo(_, ExitId))
->
exit_node_from_id(Store, ExitId, exit(_, _, NextId, Atom, _)),
get_answers(Store, NextId, [Atom | As0], As)
;
As = As0
).
:- pred trace_root_e_bug(wrap(S), edt_node(R), decl_e_bug)
<= annotated_trace(S, R).
:- mode trace_root_e_bug(in, in, out) is det.
trace_root_e_bug(wrap(S), dynamic(Ref), Bug) :-
det_edt_node_from_id(S, Ref, Node),
(
Node = exit(_, _, _, Atom, Event),
Bug = incorrect_contour(Atom, unit, Event)
;
Node = fail(_, CallId, _, Event),
call_node_from_id(S, CallId, Call),
Call = call(_, _, CallAtom, _, _, _, _),
Bug = partially_uncovered_atom(CallAtom, Event)
;
Node = excp(_, CallId, _, Exception, Event),
call_node_from_id(S, CallId, Call),
Call = call(_, _, CallAtom, _, _, _, _),
Bug = unhandled_exception(CallAtom, Exception, Event)
).
:- pred trace_children(wrap(S), edt_node(R), list(edt_node(R)))
<= annotated_trace(S, R).
:- mode trace_children(in, in, out) is semidet.
trace_children(wrap(Store), dynamic(Ref), Children) :-
det_edt_node_from_id(Store, Ref, Node),
(
Node = fail(PrecId, CallId, _, _),
not_at_depth_limit(Store, CallId),
missing_answer_children(Store, PrecId, [], Children)
;
Node = exit(PrecId, CallId, _, _, _),
not_at_depth_limit(Store, CallId),
wrong_answer_children(Store, PrecId, [], Children)
;
Node = excp(PrecId, CallId, _, _, _),
not_at_depth_limit(Store, CallId),
unexpected_exception_children(Store, PrecId, [], Children)
).
:- pred not_at_depth_limit(S, R) <= annotated_trace(S, R).
:- mode not_at_depth_limit(in, in) is semidet.
not_at_depth_limit(Store, Ref) :-
call_node_from_id(Store, Ref, call(_, _, _, _, _, no, _)).
:- pred wrong_answer_children(S, R, list(edt_node(R)), list(edt_node(R)))
<= annotated_trace(S, R).
:- mode wrong_answer_children(in, in, in, out) is det.
wrong_answer_children(Store, NodeId, Ns0, Ns) :-
det_trace_node_from_id(Store, NodeId, Node),
(
( Node = call(_, _, _, _, _, _, _)
; Node = neg(_, _, _)
; Node = cond(_, _, failed)
)
->
%
% We have reached the end of the contour.
%
Ns = Ns0
;
Node = excp(_, _, _, _, _)
->
error("wrong_answer_children: exception handling not supported")
;
(
Node = exit(_, _, _, _, _)
->
%
% Add a child for this node.
%
Ns1 = [dynamic(NodeId) | Ns0]
;
( Node = else(Prec, _)
; Node = neg_succ(Prec, _)
)
->
%
% There is a nested context.
%
missing_answer_children(Store, Prec, Ns0, Ns1)
;
Ns1 = Ns0
),
Next = step_left_in_contour(Store, Node),
wrong_answer_children(Store, Next, Ns1, Ns)
).
:- pred missing_answer_children(S, R, list(edt_node(R)), list(edt_node(R)))
<= annotated_trace(S, R).
:- mode missing_answer_children(in, in, in, out) is det.
missing_answer_children(Store, NodeId, Ns0, Ns) :-
det_trace_node_from_id(Store, NodeId, Node),
(
( Node = call(_, _, _, _, _, _, _)
; Node = neg(_, _, _)
; Node = cond(_, _, failed)
)
->
%
% We have reached the boundary of the stratum.
%
Ns = Ns0
;
Node = excp(_, _, _, _, _)
->
error(
"missing_answer_children: exception handling not supported")
;
(
( Node = exit(_, _, _, _, _)
; Node = fail(_, _, _, _)
)
->
%
% Add a child for this node.
%
Ns1 = [dynamic(NodeId) | Ns0]
;
Node = neg_fail(Prec, _)
->
%
% There is a nested successful context.
%
wrong_answer_children(Store, Prec, Ns0, Ns1)
;
( Node = else(Prec, _)
; Node = neg_succ(Prec, _)
)
->
%
% There is a nested failed context.
%
missing_answer_children(Store, Prec, Ns0, Ns1)
;
Ns1 = Ns0
),
Next = step_in_stratum(Store, Node),
missing_answer_children(Store, Next, Ns1, Ns)
).
:- pred unexpected_exception_children(S, R, list(edt_node(R)),
list(edt_node(R))) <= annotated_trace(S, R).
:- mode unexpected_exception_children(in, in, in, out) is det.
unexpected_exception_children(Store, NodeId, Ns0, Ns) :-
det_trace_node_from_id(Store, NodeId, Node),
(
( Node = call(_, _, _, _, _, _, _)
; Node = neg(_, _, failed)
; Node = cond(_, _, failed)
)
->
%
% We have reached the end of the contour.
%
Ns = Ns0
;
(
( Node = exit(_, _, _, _, _)
; Node = excp(_, _, _, _, _)
)
->
%
% Add a child for this node.
%
Ns1 = [dynamic(NodeId) | Ns0]
;
( Node = else(Prec, _)
; Node = neg_succ(Prec, _)
)
->
%
% There is a nested context.
%
missing_answer_children(Store, Prec, Ns0, Ns1)
;
Ns1 = Ns0
),
Next = step_left_in_contour(Store, Node),
unexpected_exception_children(Store, Next, Ns1, Ns)
).
%-----------------------------------------------------------------------------%
%
% Tracking a subterm dependency.
%
% We are given an EDT node, an argument position, and a path to the selected
% subterm. We wish to find the origin of that subterm within the body of the
% given node, or within the body of its parent. We can figure out the mode of
% the top of the selected subterm; if the mode is `in', the origin could be:
% - a unification within the body of the parent,
% - an output subterm in a sibling node, or
% - an input subterm of the parent node.
% In this case we look at the contour leading up to the call event associated
% with the given node. If the mode is `out', the origin could be:
% - a unification within the body of the call,
% - an output subterm of a child of the node, or
% - an input subterm of the node itself.
% In the case we look at the contour leading up to the exit or exception event
% associated with the given node.
%
% If the contour starts with a neg or cond event, then we also look at the
% contour leading up to that event (and so on, recursively). We eventually
% stop when a call event is reached. The goal representation used comes from
% this call event.
%
% We first make a full pass of the contour(s), matching up the contour events
% with atomic events in the goal representation, and constructing a list of
% `atom_info's, information about atomic goals in the contour(s). We then
% traverse this list, keeping track of the variable which contains the
% selected subterm, and the location within this variable.
:- pred trace_dependency(wrap(S), edt_node(R), arg_pos, term_path,
subterm_mode, subterm_origin(edt_node(R)))
<= annotated_trace(S, R).
:- mode trace_dependency(in, in, in, in, out, out) is det.
trace_dependency(wrap(Store), dynamic(Ref), ArgPos, TermPath, Mode, Origin) :-
det_edt_node_from_id(Store, Ref, Node),
(
Node = exit(ExitPrec, CallId, _, ExitAtom, _),
call_node_from_id(Store, CallId, Call),
Call = call(CallPrec, _, CallAtom, _, _, _, _),
(
trace_atom_subterm_is_ground(CallAtom, ArgPos, TermPath)
->
Mode = subterm_in,
Start = CallPrec
;
trace_atom_subterm_is_ground(ExitAtom, ArgPos, TermPath)
->
Mode = subterm_out,
Start = ExitPrec
;
error("trace_dependency: wrong answer subterm unbound")
)
;
Node = fail(_, CallId, _, _),
call_node_from_id(Store, CallId, Call),
Call = call(CallPrec, _, CallAtom, _, _, _, _),
(
trace_atom_subterm_is_ground(CallAtom, ArgPos, TermPath)
->
Mode = subterm_in,
Start = CallPrec
;
error(
"trace_dependency: missing answer subterm unbound")
)
;
Node = excp(_, CallId, _, _, _),
call_node_from_id(Store, CallId, Call),
Call = call(CallPrec, _, CallAtom, _, _, _, _),
%
% XXX we don't yet handle tracking of the exception value.
%
(
trace_atom_subterm_is_ground(CallAtom, ArgPos, TermPath)
->
Mode = subterm_in,
Start = CallPrec
;
error("trace_dependency: exception subterm unbound")
)
),
contour_foldl2(Store, process_trace_event, Start, next_contour(Store),
GoalCont, AtomInfo0),
(
GoalCont = unknown_goal
->
%
% There was no goal_rep to match the contour up with, so the
% origin cannot be found.
%
Origin = not_found
;
%
% Use up any remaining goals which are not associated with
% any events (e.g. unifications).
%
process_non_event_goals(GoalCont, MaybeCallArgs, AtomInfo,
AtomInfo0),
(
Mode = subterm_in,
MaybeCallArgs = yes(CallArgs)
->
list__index1_det(CallArgs, ArgPos, VarRep)
;
Mode = subterm_out,
MaybeCallArgs = no
->
%
% Headvars have the same number as their argument
% position.
%
VarRep = ArgPos
;
error("trace_dependency: contour mismatch")
),
Origin = find_subterm_origin(AtomInfo, VarRep, TermPath)
).
% contour_foldl2(Store, Pred, Right, Init, A, B) is analogous to
% other foldl2 predicates which keep track of two accumulators
% over a sequence. In this case the sequence is the contour defined
% by Right, the rightmost event of the contour. The main difference
% is that instead of supplying the initial accumulator values, the
% Init closure calculates them from the event at the left boundary
% of the contour.
%
% The mode that we have chosen has the last two arguments of the
% accumulator predicate (second argument) with the opposite modes to
% normal. This is so the accumulator predicate can construct a
% list using the DCG syntax.
%
:- pred contour_foldl2(S, pred(R, trace_node(R), A, A, B, B), R,
pred(trace_node(R), A, B), A, B) <= annotated_trace(S, R).
:- mode contour_foldl2(in, pred(in, in, in, out, out, in) is det, in,
pred(in, out, out) is det, out, out) is det.
contour_foldl2(Store, ProcessEvent, Ref, Init, A, B) :-
det_trace_node_from_id(Store, Ref, Node),
(
( Node = call(_, _, _, _, _, _, _)
; Node = neg(_, _, _)
; Node = cond(_, _, failed)
)
->
Init(Node, A, B)
;
Next = step_left_in_contour(Store, Node),
contour_foldl2(Store, ProcessEvent, Next, Init, A0, B0),
ProcessEvent(Ref, Node, A0, A, B, B0)
).
% This type represents the remainder of a goal after some of it
% has been executed, like a continuation. We don't actually
% execute this code, but match it up with the remainder of a contour
% after some events have been processed.
%
:- type goal_cont
---> subgoal_cont(
goal_rep, % A subgoal to execute.
goal_cont % Code after the subgoal.
)
; conj_cont(
list(goal_rep), % The rest of a conjunction to execute.
goal_cont % Code after the conjunction.
)
; ite_cont(
goal_rep, % Then branch.
goal_rep, % Else branch.
goal_cont % Code after the if-then-else.
)
; neg_cont(
goal_cont % Code after the negation.
)
; return % End of the procedure.
; unknown_goal. % We don't have access to the
% program representation.
:- type atom_info(R)
---> call_info(R, goal_rep)
; unify_info(goal_rep).
:- pred next_contour(S, trace_node(R), goal_cont, list(atom_info(R)))
<= annotated_trace(S, R).
:- mode next_contour(in, in, out, out) is det.
next_contour(Store, Node, Cont, AtomInfo) :-
(
Node = call(_, _, _, _, _, _, MaybeGoal)
->
AtomInfo = [],
(
MaybeGoal = yes(Goal)
->
Cont = subgoal_cont(Goal, return)
;
Cont = unknown_goal
)
;
( Node = neg(Prec, _, _)
; Node = cond(Prec, _, _)
)
->
%
% We continue into the next contour up, since the subterm
% could have come from there.
%
contour_foldl2(Store, process_trace_event, Prec,
next_contour(Store), Cont, AtomInfo)
;
error("next_contour: not a contour boundary")
).
% Match the goal_cont up with one trace event, leaving a new
% goal_cont.
%
:- pred process_trace_event(R, trace_node(R), goal_cont, goal_cont,
list(atom_info(R)), list(atom_info(R))).
:- mode process_trace_event(in, in, in, out, out, in) is det.
process_trace_event(Ref, Event, subgoal_cont(Goal, Cont0), Cont) -->
process_trace_event_goal(Ref, Event, Goal, Cont0, Cont).
process_trace_event(Ref, Event, conj_cont([], Cont0), Cont) -->
process_trace_event(Ref, Event, Cont0, Cont).
process_trace_event(Ref, Event, conj_cont([G | Gs], Cont0), Cont) -->
process_trace_event_goal(Ref, Event, G, conj_cont(Gs, Cont0), Cont).
process_trace_event(_, Event, ite_cont(Then, Else, Cont0), Cont) -->
{
Event = then(_, _)
->
Cont = subgoal_cont(Then, Cont0)
;
Event = else(_, _)
->
Cont = subgoal_cont(Else, Cont0)
;
error("process_trace_event: ite mismatch")
}.
process_trace_event(_, _, neg_cont(_), _) -->
{ error("process_trace_event: unexpected end of negation") }.
process_trace_event(_, _, return, _) -->
{ error("process_trace_event: unexpected end of goal") }.
process_trace_event(_, _, unknown_goal, unknown_goal) -->
[].
:- pred process_trace_event_goal(R, trace_node(R), goal_rep, goal_cont,
goal_cont, list(atom_info(R)), list(atom_info(R))).
:- mode process_trace_event_goal(in, in, in, in, out, out, in) is det.
process_trace_event_goal(Ref, Event, conj_rep([]), Cont0, Cont) -->
process_trace_event(Ref, Event, Cont0, Cont).
process_trace_event_goal(Ref, Event, conj_rep([G | Gs]), Cont0, Cont) -->
process_trace_event_goal(Ref, Event, G, conj_cont(Gs, Cont0), Cont).
process_trace_event_goal(_, Event, disj_rep(Ds), Cont0, Cont) -->
{ list__index1_det(Ds, disj_event_branch_number(Event), D) },
{ Cont = subgoal_cont(D, Cont0) }.
process_trace_event_goal(_, Event, switch_rep(As), Cont0, Cont) -->
{ list__index1_det(As, switch_event_branch_number(Event), A) },
{ Cont = subgoal_cont(A, Cont0) }.
process_trace_event_goal(_, Event, ite_rep(Cond, Then, Else), Cont0, Cont) -->
{
Event = cond(_, _, _)
->
Cont = subgoal_cont(Cond, ite_cont(Then, Else, Cont0))
;
Event = else(_, _)
->
%
% The contour stepped over the (failed) condition.
%
Cont = subgoal_cont(Else, Cont0)
;
error("process_trace_event_goal: ite mismatch")
}.
process_trace_event_goal(Ref, Event, negation_rep(Goal), Cont0, Cont) -->
(
{ Event = neg_succ(_, _) }
->
{ Cont = Cont0 }
;
process_trace_event_goal(Ref, Event, Goal, neg_cont(Cont0),
Cont)
).
process_trace_event_goal(Ref, Event, some_rep(Goal), Cont0, Cont) -->
process_trace_event_goal(Ref, Event, Goal, Cont0, Cont).
process_trace_event_goal(Ref, Event, GoalRep, Cont0, Cont) -->
{ GoalRep = atomic_goal_rep(_, _, _, _, AtomicGoal) },
(
{ atomic_goal_rep_is_call(AtomicGoal, _) }
->
{
Event = exit(_, _, _, _, _)
->
Cont = Cont0
;
error("process_trace_event_goal: exit mismatch")
},
[ call_info(Ref, GoalRep) ]
;
[ unify_info(GoalRep) ],
process_trace_event(Ref, Event, Cont0, Cont)
).
:- pred process_non_event_goals(goal_cont, maybe(list(var_rep)),
list(atom_info(R)), list(atom_info(R))).
:- mode process_non_event_goals(in, out, out, in) is det.
process_non_event_goals(subgoal_cont(Goal, Cont), MaybeArgs) -->
process_non_event_goals_2(Goal, Cont, MaybeArgs).
process_non_event_goals(conj_cont([], Cont), MaybeArgs) -->
process_non_event_goals(Cont, MaybeArgs).
process_non_event_goals(conj_cont([G | Gs], Cont), MaybeArgs) -->
process_non_event_goals_2(G, conj_cont(Gs, Cont), MaybeArgs).
process_non_event_goals(ite_cont(_, _, _), _) -->
{ error("process_non_event_goals: ite event expected") }.
process_non_event_goals(neg_cont(_), _) -->
{ error("process_non_event_goals: neg event expected") }.
process_non_event_goals(return, no) -->
[].
process_non_event_goals(unknown_goal, _) -->
{ error("process_non_event_goals: goal is unknown") }.
:- pred process_non_event_goals_2(goal_rep, goal_cont, maybe(list(var_rep)),
list(atom_info(R)), list(atom_info(R))).
:- mode process_non_event_goals_2(in, in, out, out, in) is det.
process_non_event_goals_2(conj_rep([]), Cont, MaybeArgs) -->
process_non_event_goals(Cont, MaybeArgs).
process_non_event_goals_2(conj_rep([G | Gs]), Cont, MaybeArgs) -->
process_non_event_goals_2(G, conj_cont(Gs, Cont), MaybeArgs).
process_non_event_goals_2(disj_rep(_), _, _) -->
{ error("process_non_event_goals_2: disj event expected") }.
process_non_event_goals_2(switch_rep(_), _, _) -->
{ error("process_non_event_goals_2: swtc event expected") }.
process_non_event_goals_2(ite_rep(_, _, _), _, _) -->
{ error("process_non_event_goals_2: cond event expected") }.
process_non_event_goals_2(negation_rep(Goal), Cont, MaybeArgs) -->
process_non_event_goals_2(Goal, neg_cont(Cont), MaybeArgs).
process_non_event_goals_2(some_rep(Goal), Cont, MaybeArgs) -->
process_non_event_goals_2(Goal, Cont, MaybeArgs).
process_non_event_goals_2(Goal, Cont, MaybeArgs) -->
{ Goal = atomic_goal_rep(_, _, _, _, AtomicGoal) },
(
{ atomic_goal_rep_is_call(AtomicGoal, Args) }
->
{ MaybeArgs = yes(Args) }
;
process_non_event_goals(Cont, MaybeArgs),
[ unify_info(Goal) ]
).
% Scan through the information derived from the contour, and
% track the location of the selected subterm.
%
:- func find_subterm_origin(list(atom_info(R)), var_rep, term_path)
= subterm_origin(edt_node(R)).
find_subterm_origin([], VarRep, TermPath) = input(VarRep, TermPath).
find_subterm_origin([unify_info(Goal) | AtomInfo], VarRep, TermPath)
= Origin :-
(
Goal = atomic_goal_rep(_, File, Line, BoundVars, AtomicGoal),
list__member(VarRep, BoundVars)
->
Origin = find_subterm_origin_unify(File, Line, AtomicGoal,
AtomInfo, VarRep, TermPath)
;
Origin = find_subterm_origin(AtomInfo, VarRep, TermPath)
).
find_subterm_origin([call_info(Ref, Goal) | AtomInfo], VarRep, TermPath)
= Origin :-
(
Goal = atomic_goal_rep(_, _, _, BoundVars, AtomicGoal),
list__member(VarRep, BoundVars)
->
Origin = find_subterm_origin_call(Ref, AtomicGoal, VarRep,
TermPath)
;
Origin = find_subterm_origin(AtomInfo, VarRep, TermPath)
).
:- func find_subterm_origin_unify(string, int, atomic_goal_rep,
list(atom_info(R)), var_rep, term_path)
= subterm_origin(edt_node(R)).
find_subterm_origin_unify(File, Line, unify_construct_rep(_, _, Args),
AtomInfo, _, TermPath0) = Origin :-
(
TermPath0 = [ArgPos | TermPath],
list__index1_det(Args, ArgPos, VarRep),
Origin = find_subterm_origin(AtomInfo, VarRep, TermPath)
;
TermPath0 = [],
Origin = unification(File, Line)
).
find_subterm_origin_unify(_, _, unify_deconstruct_rep(VarRep, _, Args),
AtomInfo, VarRep0, TermPath0) = Origin :-
(
list__nth_member_search(Args, VarRep0, ArgPos)
->
TermPath = [ArgPos | TermPath0],
Origin = find_subterm_origin(AtomInfo, VarRep, TermPath)
;
error("find_subterm_origin_unify: arg not found")
).
find_subterm_origin_unify(_, _, unify_assign_rep(_, Source), AtomInfo, _,
TermPath) = find_subterm_origin(AtomInfo, Source, TermPath).
find_subterm_origin_unify(_, _, unify_simple_test_rep(_, _), _, _, _) = _ :-
error("find_subterm_origin_unify: unexpected test").
find_subterm_origin_unify(_, _, pragma_foreign_code_rep(_), _, _, _) = _ :-
error("find_subterm_origin_unify: unexpected pragma call").
find_subterm_origin_unify(_, _, higher_order_call_rep(_, _), _, _, _) = _ :-
error("find_subterm_origin_unify: unexpected ho call").
find_subterm_origin_unify(_, _, method_call_rep(_, _, _), _, _, _) = _ :-
error("find_subterm_origin_unify: unexpected method call").
find_subterm_origin_unify(_, _, plain_call_rep(_, _), _, _, _) = _ :-
error("find_subterm_origin_unify: unexpected call").
:- func find_subterm_origin_call(R, atomic_goal_rep, var_rep, term_path)
= subterm_origin(edt_node(R)).
find_subterm_origin_call(Ref, Call, VarRep, TermPath) = Origin :-
(
atomic_goal_rep_is_call(Call, Args),
list__nth_member_search(Args, VarRep, ArgPos)
->
Origin = output(dynamic(Ref), ArgPos, TermPath)
;
error("find_subterm_origin_call: arg not found")
).
%-----------------------------------------------------------------------------%
:- pred edt_subtree_details(S, edt_node(R), event_number, sequence_number)
<= annotated_trace(S, R).
:- mode edt_subtree_details(in, in, out, out) is det.
edt_subtree_details(Store, dynamic(Ref), Event, SeqNo) :-
det_edt_node_from_id(Store, Ref, Node),
(
Node = exit(_, Call, _, _, Event)
;
Node = fail(_, Call, _, Event)
;
Node = excp(_, Call, _, _, Event)
),
call_node_from_id(Store, Call, call(_, _, _, SeqNo, _, _, _)).
:- inst trace_node_edt_node =
bound( exit(ground, ground, ground, ground, ground)
; fail(ground, ground, ground, ground)
; excp(ground, ground, ground, ground, ground)).
:- pred det_edt_node_from_id(S, R, trace_node(R)) <= annotated_trace(S, R).
:- mode det_edt_node_from_id(in, in, out(trace_node_edt_node)) is det.
det_edt_node_from_id(Store, Ref, Node) :-
(
trace_node_from_id(Store, Ref, Node0),
(
Node0 = exit(_, _, _, _, _)
;
Node0 = fail(_, _, _, _)
;
Node0 = excp(_, _, _, _, _)
)
->
Node = Node0
;
error("det_edt_node_from_id: not an EXIT, FAIL or EXCP node")
).
:- pred trace_atom_subterm_is_ground(trace_atom, arg_pos, term_path).
:- mode trace_atom_subterm_is_ground(in, in, in) is semidet.
trace_atom_subterm_is_ground(atom(_, _, Args), ArgPos, _) :-
list__index1_det(Args, ArgPos, yes(_)).
:- func disj_event_branch_number(trace_node(R)) = int.
disj_event_branch_number(Node) = N :-
(
(
Node = first_disj(_, Str)
;
Node = later_disj(_, Str, _)
),
list__last(string__words(is_semicolon, Str), LastStepStr),
path_step_from_string(LastStepStr, disj(N0))
->
N = N0
;
error("disj_event_branch_number: not a DISJ event")
).
:- func switch_event_branch_number(trace_node(R)) = int.
switch_event_branch_number(Node) = N :-
(
Node = switch(_, Str),
list__last(string__words(is_semicolon, Str), LastStepStr),
path_step_from_string(LastStepStr, switch(N0))
->
N = N0
;
error("switch_event_branch_number: not a SWTC event")
).
:- pred is_semicolon(char).
:- mode is_semicolon(in) is semidet.
is_semicolon(';').
%-----------------------------------------------------------------------------%
:- pred decl_bug_get_event_number(decl_bug, event_number).
:- mode decl_bug_get_event_number(in, out) is det.
decl_bug_get_event_number(e_bug(EBug), Event) :-
(
EBug = incorrect_contour(_, _, Event)
;
EBug = partially_uncovered_atom(_, Event)
;
EBug = unhandled_exception(_, _, Event)
).
decl_bug_get_event_number(i_bug(IBug), Event) :-
IBug = inadmissible_call(_, _, _, Event).