mirror of
https://github.com/Mercury-Language/mercury.git
synced 2025-12-14 13:23:53 +00:00
Estimated hours taken: 48
Branches: main
Make I/O actions known to the declarative debugger. The debugger doesn't do
anything with them yet beyond asking about their correctness.
browser/io_action.m:
New module for representing I/O actions, and for constructing the map
from I/O action numbers to the actions themselves.
browser/mdb.m:
Include the new module.
browser/declarative_analysis.m:
Make the map from I/O action numbers to the actions themselves part
of the analyzer state, since conversions from annotated trace nodes
to EDT nodes may now require this information. This information is
stored in the analyzer state because only the analyser needs this
information (when converting annotated trace nodes to EDT tree nodes).
It is not stored in the trace node store because its lifetime is
different: its contents does not change during a tree deepening
operation.
browser/declarative_execution.m:
Store the current value of the I/O action counter with each call and
exit node. The list of I/O actions associated with the atom of the exit
node is given by the I/O actions whose counters lie between these two
values (initial inclusive, final exclusive).
browser/declarative_debugger.m:
browser/declarative_oracle.m:
Distinguish atoms associated with exit nodes from atoms associated with
call nodes, since the former, but not the latter, now have a list of
I/O actions associated with them.
browser/declarative_user.m:
Add mechanisms for printing and browsing the I/O actions associated
with EDT nodes and bugs.
runtime/mercury_trace_base.[ch]:
Move the code for finding an I/O action here from the file
mercury_trace_declarative.c, for use by browser/io_action.m.
runtime/mercury_layout_util.[ch]:
Move a utility function here from mercury_trace_declarative.c,
for use by the code moved to mercury_trace_base.c.
trace/mercury_trace_declarative.c:
When invoking the front end, pass to it the boundaries of the required
I/O action map. Cache these boundaries, so we can tell the front end
when reinvocation of the back end by the front end (to materialize
previously virtual parts of the annotated trace) does not require
the reconstruction of the I/O action map.
trace/mercury_trace_vars.[ch]:
Separate out the code for finding an I/O action from the code for
browsing it, for use in mercury_trace_declarative.c.
Note places where the implementation does not live up to the
documentation.
trace/mercury_trace.[ch]:
Add a parameter to MR_trace_retry that allows retries to cross I/O
actions without asking the user if this is OK.
trace/mercury_trace_internal.c:
trace/mercury_trace_external.c:
Pass MR_FALSE as this new parameter to MR_trace_retry.
tests/debugger/declarative/tabled_read_decl.{m,inp,exp}:
A slightly modified copy of the tests/debugger/tabled_read_decl test
case, to check the declarative debugger's handling of goals with I/O
actions.
tests/debugger/declarative/Mmakefile:
Enable the new test case.
1369 lines
42 KiB
Mathematica
1369 lines
42 KiB
Mathematica
%-----------------------------------------------------------------------------%
|
|
% Copyright (C) 1999-2002 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 mdb__declarative_execution, mdb__program_representation.
|
|
:- import_module mdb__io_action.
|
|
:- import_module io, bool, list, std_util.
|
|
|
|
% 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(
|
|
final_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(
|
|
init_decl_atom, % The called atom, in its initial
|
|
% state.
|
|
event_number % The fail event.
|
|
)
|
|
; unhandled_exception(
|
|
init_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(
|
|
init_decl_atom, % The parent atom, in its initial
|
|
% state.
|
|
decl_position, % The location of the call in the
|
|
% parent's body.
|
|
init_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 the following two types represent questions from the
|
|
% analyser to the oracle about some aspect of program behaviour,
|
|
% and responses from the oracle, respectively. In both cases the
|
|
% type parameter is for the type of EDT nodes -- each question and
|
|
% answer keeps a reference to the node which generated it, so that
|
|
% the analyser is able to figure out what to do when the answer
|
|
% arrives back from the oracle.
|
|
%
|
|
:- type decl_question(T)
|
|
% The node is a suspected wrong answer. The first
|
|
% argument is the EDT node the question came from.
|
|
% The second argument is the atom in its final
|
|
% state of instantiatedness (ie. at the EXIT event).
|
|
%
|
|
---> wrong_answer(T, final_decl_atom)
|
|
|
|
% The node is a suspected missing answer. The
|
|
% first argument is the EDT node the question came
|
|
% from. The second argument is the atom in its
|
|
% initial state of instantiatedness (ie. at the
|
|
% CALL event), and the third argument is the list
|
|
% of solutions.
|
|
%
|
|
; missing_answer(T, init_decl_atom, list(final_decl_atom))
|
|
|
|
% The node is a possibly unexpected exception.
|
|
% The first argument is the EDT node the question
|
|
% came from. The second argument is the atom in
|
|
% its initial state of instantiation, and the third
|
|
% argument is the exception thrown.
|
|
%
|
|
; unexpected_exception(T, init_decl_atom, decl_exception).
|
|
|
|
:- type decl_answer(T)
|
|
% The oracle knows the truth value of this node.
|
|
%
|
|
---> truth_value(T, decl_truth)
|
|
|
|
% The oracle does not say anything about the truth
|
|
% value, but is suspicious of the subterm at the
|
|
% given term_path and arg_pos.
|
|
%
|
|
; suspicious_subterm(T, arg_pos, term_path).
|
|
|
|
% Extract the EDT node from a question.
|
|
%
|
|
:- func get_decl_question_node(decl_question(T)) = T.
|
|
|
|
:- type some_decl_atom
|
|
---> init(init_decl_atom)
|
|
; final(final_decl_atom).
|
|
|
|
:- type init_decl_atom
|
|
---> init_decl_atom(
|
|
init_atom :: trace_atom
|
|
).
|
|
|
|
:- type final_decl_atom
|
|
---> final_decl_atom(
|
|
final_atom :: trace_atom,
|
|
final_io_actions :: list(io_action)
|
|
).
|
|
|
|
:- 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_action_map::in, io__input_stream::in,
|
|
io__output_stream::in, diagnoser_state(R)::out) is det.
|
|
|
|
:- pred diagnosis(S::in, R::in, int::in, int::in, int::in,
|
|
diagnoser_response::out,
|
|
diagnoser_state(R)::in, diagnoser_state(R)::out,
|
|
io__state::di, io__state::uo) is cc_multi <= annotated_trace(S, R).
|
|
|
|
:- pred unravel_decl_atom(some_decl_atom::in, trace_atom::out,
|
|
list(io_action)::out) is det.
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
|
|
:- implementation.
|
|
:- import_module mdb__declarative_analyser, mdb__declarative_oracle.
|
|
:- import_module require, int, char, string, assoc_list, map.
|
|
|
|
unravel_decl_atom(DeclAtom, TraceAtom, IoActions) :-
|
|
(
|
|
DeclAtom = init(init_decl_atom(TraceAtom)),
|
|
IoActions = []
|
|
;
|
|
DeclAtom = final(final_decl_atom(TraceAtom, IoActions))
|
|
).
|
|
|
|
get_decl_question_node(wrong_answer(Node, _)) = Node.
|
|
get_decl_question_node(missing_answer(Node, _, _)) = Node.
|
|
get_decl_question_node(unexpected_exception(Node, _, _)) = Node.
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
|
|
:- type diagnoser_state(R)
|
|
---> diagnoser(
|
|
analyser_state :: analyser_state(edt_node(R)),
|
|
oracle_state :: 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(IoActionMap, InStr, OutStr, Diagnoser) :-
|
|
analyser_state_init(IoActionMap, Analyser),
|
|
oracle_state_init(InStr, OutStr, Oracle),
|
|
Diagnoser = diagnoser(Analyser, Oracle).
|
|
|
|
diagnosis(Store, NodeId, UseOldIoActionMap, IoActionStart, IoActionEnd,
|
|
Response, Diagnoser0, Diagnoser) -->
|
|
( { UseOldIoActionMap > 0 } ->
|
|
{ Diagnoser1 = Diagnoser0 },
|
|
{ diagnoser_get_analyser(Diagnoser1, Analyser1) }
|
|
;
|
|
make_io_action_map(IoActionStart, IoActionEnd, IoActionMap),
|
|
{ Analyser0 = Diagnoser0 ^ analyser_state },
|
|
{ analyser_state_replace_io_map(IoActionMap,
|
|
Analyser0, Analyser1) },
|
|
{ Diagnoser1 = Diagnoser0 ^ analyser_state := Analyser1 }
|
|
),
|
|
{ start_analysis(wrap(Store), dynamic(NodeId), AnalyserResponse,
|
|
Analyser1, Analyser) },
|
|
{ diagnoser_set_analyser(Diagnoser1, Analyser, Diagnoser2) },
|
|
{ debug_analyser_state(Analyser, MaybeOrigin) },
|
|
handle_analyser_response(Store, AnalyserResponse, MaybeOrigin,
|
|
Response, Diagnoser2, Diagnoser).
|
|
|
|
:- pred handle_analyser_response(S::in, analyser_response(edt_node(R))::in,
|
|
maybe(subterm_origin(edt_node(R)))::in, diagnoser_response::out,
|
|
diagnoser_state(R)::in, diagnoser_state(R)::out,
|
|
io__state::di, io__state::uo) is cc_multi <= annotated_trace(S, R).
|
|
|
|
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), MaybeOrigin, Response,
|
|
Diagnoser0, Diagnoser) -->
|
|
{ diagnoser_get_oracle(Diagnoser0, Oracle0) },
|
|
debug_origin(Flag),
|
|
(
|
|
{ MaybeOrigin = yes(Origin) },
|
|
{ Flag > 0 }
|
|
->
|
|
io__write_string("Origin: "),
|
|
write_origin(wrap(Store), Origin),
|
|
io__nl
|
|
;
|
|
[]
|
|
),
|
|
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::in, oracle_response(edt_node(R))::in,
|
|
diagnoser_response::out,
|
|
diagnoser_state(R)::in, diagnoser_state(R)::out,
|
|
io__state::di, io__state::uo) is cc_multi <= annotated_trace(S, R).
|
|
|
|
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) },
|
|
{ debug_analyser_state(Analyser, MaybeOrigin) },
|
|
handle_analyser_response(Store, AnalyserResponse, MaybeOrigin,
|
|
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::in, diagnoser_response::out,
|
|
diagnoser_state(R)::in, diagnoser_state(R)::out,
|
|
io__state::di, io__state::uo) is cc_multi.
|
|
|
|
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(map__init, InStr, OutStr, Diagnoser).
|
|
|
|
% Export a monomorphic version of diagnosis/10, to make it
|
|
% easier to call from C code.
|
|
%
|
|
:- pred diagnosis_store(trace_node_store::in, trace_node_id::in,
|
|
int::in, int::in, int::in, diagnoser_response::out,
|
|
diagnoser_state(trace_node_id)::in,
|
|
diagnoser_state(trace_node_id)::out, io__state::di, io__state::uo)
|
|
is cc_multi.
|
|
|
|
:- pragma export(diagnosis_store(in, in, in, in, in, out, in, out, di, uo),
|
|
"MR_DD_decl_diagnosis").
|
|
|
|
diagnosis_store(Store, Node, UseOldIoActionMap, IoActionStart, IoActionEnd,
|
|
Response, State0, State) -->
|
|
diagnosis(Store, Node, UseOldIoActionMap, IoActionStart, IoActionEnd,
|
|
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.
|
|
%
|
|
|
|
% The type of nodes in our implementation of EDTs. The parameter
|
|
% is meant to be the type of references to trace nodes. In
|
|
% particular, the references should be to trace nodes that could
|
|
% be considered nodes in the EDT, namely those for exit, fail
|
|
% and exception events.
|
|
%
|
|
:- type edt_node(R)
|
|
---> dynamic(R).
|
|
|
|
:- instance mercury_edt(wrap(S), edt_node(R)) <= annotated_trace(S, R)
|
|
where [
|
|
pred(edt_root_question/4) is trace_root_question,
|
|
pred(edt_root_e_bug/4) 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).
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
|
|
:- func exit_node_decl_atom(io_action_map::in, S::in,
|
|
trace_node(R)::in(trace_node_exit)) = (final_decl_atom::out) is det
|
|
<= annotated_trace(S, R).
|
|
|
|
exit_node_decl_atom(IoActionMap, Store, ExitNode) = DeclAtom :-
|
|
ExitAtom = ExitNode ^ exit_atom,
|
|
CallId = ExitNode ^ exit_call,
|
|
call_node_from_id(Store, CallId, Call),
|
|
CallIoSeq = Call ^ call_io_seq_num,
|
|
ExitIoSeq = ExitNode ^ exit_io_seq_num,
|
|
IoActions = make_io_actions(IoActionMap, CallIoSeq, ExitIoSeq),
|
|
DeclAtom = final_decl_atom(ExitAtom, IoActions).
|
|
|
|
:- func call_node_decl_atom(S, R) = init_decl_atom <= annotated_trace(S, R).
|
|
|
|
call_node_decl_atom(Store, CallId) = DeclAtom :-
|
|
call_node_from_id(Store, CallId, CallNode),
|
|
CallAtom = CallNode ^ call_atom,
|
|
DeclAtom = init_decl_atom(CallAtom).
|
|
|
|
:- func make_io_actions(io_action_map, int, int) = list(io_action).
|
|
|
|
make_io_actions(IoActionMap, InitIoSeq, ExitIoSeq) =
|
|
( InitIoSeq = ExitIoSeq ->
|
|
[]
|
|
;
|
|
[map__lookup(IoActionMap, InitIoSeq) |
|
|
make_io_actions(IoActionMap, InitIoSeq + 1, ExitIoSeq)]
|
|
).
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
|
|
:- pred trace_root_question(io_action_map::in, wrap(S)::in, edt_node(R)::in,
|
|
decl_question(edt_node(R))::out) is det <= annotated_trace(S, R).
|
|
|
|
trace_root_question(IoActionMap, wrap(Store), dynamic(Ref), Root) :-
|
|
det_edt_return_node_from_id(Store, Ref, Node),
|
|
(
|
|
Node = fail(_, CallId, RedoId, _),
|
|
DeclAtom = call_node_decl_atom(Store, CallId),
|
|
get_answers(IoActionMap, Store, RedoId, [], Answers),
|
|
Root = missing_answer(dynamic(Ref), DeclAtom, Answers)
|
|
;
|
|
Node = exit(_, _, _, _, _, _),
|
|
DeclAtom = exit_node_decl_atom(IoActionMap, Store, Node),
|
|
Root = wrong_answer(dynamic(Ref), DeclAtom)
|
|
;
|
|
Node = excp(_, CallId, _, Exception, _),
|
|
DeclAtom = call_node_decl_atom(Store, CallId),
|
|
Root = unexpected_exception(dynamic(Ref), DeclAtom, Exception)
|
|
).
|
|
|
|
:- pred get_answers(io_action_map::in, S::in, R::in,
|
|
list(final_decl_atom)::in, list(final_decl_atom)::out) is det
|
|
<= annotated_trace(S, R).
|
|
|
|
get_answers(IoActionMap, Store, RedoId, DeclAtoms0, DeclAtoms) :-
|
|
(
|
|
maybe_redo_node_from_id(Store, RedoId, redo(_, ExitId))
|
|
->
|
|
exit_node_from_id(Store, ExitId, ExitNode),
|
|
NextId = ExitNode ^ exit_prev_redo,
|
|
DeclAtom = exit_node_decl_atom(IoActionMap, Store, ExitNode),
|
|
get_answers(IoActionMap, Store, NextId,
|
|
[DeclAtom | DeclAtoms0], DeclAtoms)
|
|
;
|
|
DeclAtoms = DeclAtoms0
|
|
).
|
|
|
|
:- pred trace_root_e_bug(io_action_map::in, wrap(S)::in, edt_node(R)::in,
|
|
decl_e_bug::out) is det <= annotated_trace(S, R).
|
|
|
|
trace_root_e_bug(IoActionMap, wrap(Store), dynamic(Ref), Bug) :-
|
|
det_edt_return_node_from_id(Store, Ref, Node),
|
|
(
|
|
Node = exit(_, _, _, _, Event, _),
|
|
DeclAtom = exit_node_decl_atom(IoActionMap, Store, Node),
|
|
Bug = incorrect_contour(DeclAtom, unit, Event)
|
|
;
|
|
Node = fail(_, CallId, _, Event),
|
|
DeclAtom = call_node_decl_atom(Store, CallId),
|
|
Bug = partially_uncovered_atom(DeclAtom, Event)
|
|
;
|
|
Node = excp(_, CallId, _, Exception, Event),
|
|
DeclAtom = call_node_decl_atom(Store, CallId),
|
|
Bug = unhandled_exception(DeclAtom, 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_return_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, CallNode),
|
|
CallNode ^ call_at_max_depth = 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 primitive (unification of foreign_proc) 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. This contour will be wholly within the parent call.
|
|
%
|
|
% If the mode is `out', the origin could be:
|
|
% - a primitive (unification or foreign_proc) within the body of the
|
|
% call,
|
|
% - an output subterm of a child of the node, or
|
|
% - an input subterm of the node itself.
|
|
% In this case we look at the contour leading up to the exit or exception event
|
|
% associated with the given node. This contour will be wholly within the
|
|
% current call.
|
|
%
|
|
% Our algorithm for finding the origin has three phases.
|
|
%
|
|
% In the first phase, we materialize a list of the nodes in the contour.
|
|
%
|
|
% In the second phase, we use this list of nodes to construct a list of the
|
|
% primitive goals along that contour in the body of the relevant procedure,
|
|
% leading up to either the call event (if subterm_mode is `in') or the exit
|
|
% event (if subterm_mode is `out').
|
|
%
|
|
% In the third phase, we traverse the list of primitive goals backwards, from
|
|
% the most recently executed primitive to the earliest one, keeping track of
|
|
% the variable which contains the selected subterm, and the location within
|
|
% this variable.
|
|
|
|
:- type dependency_chain_start(R)
|
|
---> chain_start(
|
|
start_loc(R),
|
|
int, % The argument number of the selected
|
|
% position in the full list of
|
|
% arguments, including the
|
|
% compiler-generated ones.
|
|
R, % The id of the node preceding the exit
|
|
% node, if start_loc is cur_goal
|
|
% and the id of the node preceding the
|
|
% call node if start_loc is
|
|
% parent_goal.
|
|
maybe(goal_path),
|
|
% No if start_loc is cur_goal;
|
|
% and yes wrapped around the goal path
|
|
% of the call in the parent procedure
|
|
% if start_loc is parent_goal.
|
|
maybe(proc_rep) % The body of the procedure indicated
|
|
% by start_loc.
|
|
).
|
|
|
|
:- type start_loc(R)
|
|
---> cur_goal
|
|
; parent_goal(R, trace_node(R)).
|
|
|
|
:- type goal_and_path ---> goal_and_path(goal_rep, goal_path).
|
|
|
|
:- type goal_and_path_list == list(goal_and_path).
|
|
|
|
:- type annotated_primitive(R)
|
|
---> primitive(
|
|
string, % filename
|
|
int, % line number
|
|
list(var_rep), % vars bound by the atomic goal
|
|
atomic_goal_rep,% the atomic goal itself
|
|
goal_path, % its goal path
|
|
maybe(R)
|
|
% if the atomic goal is a call,
|
|
% the id of the call's exit event
|
|
).
|
|
|
|
:- pred trace_dependency(wrap(S)::in, edt_node(R)::in,
|
|
arg_pos::in, term_path::in, subterm_mode::out,
|
|
subterm_origin(edt_node(R))::out) is det <= annotated_trace(S, R).
|
|
|
|
trace_dependency(wrap(Store), dynamic(Ref), ArgPos, TermPath, Mode, Origin) :-
|
|
find_chain_start(Store, Ref, ArgPos, TermPath, ChainStart),
|
|
ChainStart = chain_start(StartLoc, ArgNum, NodeId, StartPath,
|
|
MaybeProcRep),
|
|
Mode = start_loc_to_subterm_mode(StartLoc),
|
|
(
|
|
MaybeProcRep = no,
|
|
Origin = not_found
|
|
;
|
|
MaybeProcRep = yes(ProcRep),
|
|
det_trace_node_from_id(Store, NodeId, Node),
|
|
materialize_contour(Store, NodeId, Node, [], Contour0),
|
|
(
|
|
StartLoc = parent_goal(CallId, CallNode),
|
|
Contour = list__append(Contour0, [CallId - CallNode])
|
|
;
|
|
StartLoc = cur_goal,
|
|
Contour = Contour0
|
|
),
|
|
ProcRep = proc_rep(HeadVars, GoalRep),
|
|
make_primitive_list(Store, [goal_and_path(GoalRep, [])],
|
|
Contour, StartPath, ArgNum, HeadVars, Var,
|
|
[], Primitives),
|
|
traverse_primitives(Primitives, Var, TermPath,
|
|
Store, ProcRep, Origin)
|
|
).
|
|
|
|
:- pred find_chain_start(S::in, R::in, arg_pos::in, term_path::in,
|
|
dependency_chain_start(R)::out) is det <= annotated_trace(S, R).
|
|
|
|
find_chain_start(Store, Ref, ArgPos, TermPath, ChainStart) :-
|
|
det_edt_return_node_from_id(Store, Ref, Node),
|
|
(
|
|
Node = exit(_, CallId, _, ExitAtom, _, _),
|
|
call_node_from_id(Store, CallId, CallNode),
|
|
CallAtom = CallNode ^ call_atom,
|
|
( trace_atom_subterm_is_ground(CallAtom, ArgPos, TermPath) ->
|
|
find_chain_start_inside(Store, CallId, CallNode,
|
|
ArgPos, ChainStart)
|
|
; trace_atom_subterm_is_ground(ExitAtom, ArgPos, TermPath) ->
|
|
find_chain_start_outside(CallNode, Node, ArgPos,
|
|
ChainStart)
|
|
;
|
|
error("find_chain_start: unbound wrong answer term")
|
|
)
|
|
;
|
|
Node = fail(_, CallId, _, _),
|
|
call_node_from_id(Store, CallId, CallNode),
|
|
CallAtom = CallNode ^ call_atom,
|
|
( trace_atom_subterm_is_ground(CallAtom, ArgPos, TermPath) ->
|
|
find_chain_start_inside(Store, CallId, CallNode,
|
|
ArgPos, ChainStart)
|
|
;
|
|
error("find_chain_start: unbound missing answer term")
|
|
)
|
|
;
|
|
Node = excp(_, CallId, _, _, _),
|
|
call_node_from_id(Store, CallId, CallNode),
|
|
CallAtom = CallNode ^ call_atom,
|
|
%
|
|
% XXX we don't yet handle tracking of the exception value.
|
|
%
|
|
( trace_atom_subterm_is_ground(CallAtom, ArgPos, TermPath) ->
|
|
find_chain_start_inside(Store, CallId, CallNode,
|
|
ArgPos, ChainStart)
|
|
;
|
|
error("find_chain_start: unbound exception term")
|
|
)
|
|
).
|
|
|
|
:- pred find_chain_start_inside(S::in, R::in,
|
|
trace_node(R)::in(trace_node_call), arg_pos::in,
|
|
dependency_chain_start(R)::out) is det <= annotated_trace(S, R).
|
|
|
|
find_chain_start_inside(Store, CallId, CallNode, ArgPos, ChainStart) :-
|
|
CallPrecId = CallNode ^ call_preceding,
|
|
CallAtom = CallNode ^ call_atom,
|
|
CallPathStr = CallNode ^ call_goal_path,
|
|
path_from_string_det(CallPathStr, CallPath),
|
|
StartLoc = parent_goal(CallId, CallNode),
|
|
absolute_arg_num(ArgPos, CallAtom, ArgNum),
|
|
StartId = CallPrecId,
|
|
StartPath = yes(CallPath),
|
|
parent_proc_rep(Store, CallId, StartRep),
|
|
ChainStart = chain_start(StartLoc, ArgNum, StartId, StartPath,
|
|
StartRep).
|
|
|
|
:- pred find_chain_start_outside(trace_node(R)::in(trace_node_call),
|
|
trace_node(R)::in(trace_node_exit), arg_pos::in,
|
|
dependency_chain_start(R)::out) is det.
|
|
|
|
find_chain_start_outside(CallNode, ExitNode, ArgPos, ChainStart) :-
|
|
StartLoc = cur_goal,
|
|
ExitAtom = ExitNode ^ exit_atom,
|
|
absolute_arg_num(ArgPos, ExitAtom, ArgNum),
|
|
StartId = ExitNode ^ exit_preceding,
|
|
StartPath = no,
|
|
StartRep = CallNode ^ call_proc_rep,
|
|
ChainStart = chain_start(StartLoc, ArgNum, StartId,
|
|
StartPath, StartRep).
|
|
|
|
:- pred parent_proc_rep(S::in, R::in, maybe(proc_rep)::out)
|
|
is det <= annotated_trace(S, R).
|
|
|
|
parent_proc_rep(Store, CallId, ProcRep) :-
|
|
call_node_from_id(Store, CallId, Call),
|
|
CallPrecId = Call ^ call_preceding,
|
|
( trace_node_from_id(Store, CallPrecId, CallPrecNode) ->
|
|
step_left_to_call(Store, CallPrecNode, ParentCallNode),
|
|
ProcRep = ParentCallNode ^ call_proc_rep
|
|
;
|
|
% The parent call is outside the annotated trace.
|
|
ProcRep = no
|
|
).
|
|
|
|
:- pred step_left_to_call(S::in, trace_node(R)::in,
|
|
trace_node(R)::out(trace_node_call)) is det <= annotated_trace(S, R).
|
|
|
|
step_left_to_call(Store, Node, ParentCallNode) :-
|
|
( Node = call(_, _, _, _, _, _, _, _, _) ->
|
|
ParentCallNode = Node
|
|
;
|
|
( Node = neg(NegPrec, _, _) ->
|
|
PrevNodeId = NegPrec
|
|
;
|
|
PrevNodeId = step_left_in_contour(Store, Node)
|
|
),
|
|
det_trace_node_from_id(Store, PrevNodeId, PrevNode),
|
|
step_left_to_call(Store, PrevNode, ParentCallNode)
|
|
).
|
|
|
|
:- pred materialize_contour(S::in, R::in, trace_node(R)::in,
|
|
assoc_list(R, trace_node(R))::in, assoc_list(R, trace_node(R))::out)
|
|
is det <= annotated_trace(S, R).
|
|
|
|
materialize_contour(Store, NodeId, Node, Nodes0, Nodes) :-
|
|
( Node = call(_, _, _, _, _, _, _, _, _) ->
|
|
Nodes = Nodes0
|
|
;
|
|
( Node = neg(NegPrec, _, _) ->
|
|
PrevNodeId = NegPrec
|
|
;
|
|
PrevNodeId = step_left_in_contour(Store, Node)
|
|
),
|
|
det_trace_node_from_id(Store, PrevNodeId, PrevNode),
|
|
( Node = then(_, _) ->
|
|
% The cond node is enough to tell us which way the
|
|
% if-then-else went; the then node would just
|
|
% complicate the job of make_primitive_list.
|
|
Nodes1 = Nodes0
|
|
;
|
|
Nodes1 = [NodeId - Node | Nodes0]
|
|
),
|
|
materialize_contour(Store, PrevNodeId, PrevNode,
|
|
Nodes1, Nodes)
|
|
).
|
|
|
|
:- pred make_primitive_list(S::in, goal_and_path_list::in,
|
|
assoc_list(R, trace_node(R))::in, maybe(goal_path)::in,
|
|
int::in, list(var_rep)::in, var_rep::out,
|
|
list(annotated_primitive(R))::in, list(annotated_primitive(R))::out)
|
|
is det <= annotated_trace(S, R).
|
|
|
|
make_primitive_list(Store, [goal_and_path(Goal, Path) | GoalPaths],
|
|
Contour, MaybeEnd, ArgNum, HeadVars, Var,
|
|
Primitives0, Primitives) :-
|
|
(
|
|
Goal = conj_rep(Conjs),
|
|
add_paths_to_conjuncts(Conjs, Path, 1, ConjPaths),
|
|
make_primitive_list(Store, list__append(ConjPaths, GoalPaths),
|
|
Contour, MaybeEnd, ArgNum, HeadVars, Var,
|
|
Primitives0, Primitives)
|
|
;
|
|
Goal = disj_rep(Disjs),
|
|
(
|
|
Contour = [_ - ContourHeadNode | ContourTail],
|
|
( ContourHeadNode = first_disj(_, DisjPathStr)
|
|
; ContourHeadNode = later_disj(_, DisjPathStr, _)
|
|
),
|
|
path_from_string_det(DisjPathStr, DisjPath),
|
|
list__append(Path, PathTail, DisjPath),
|
|
PathTail = [disj(N)]
|
|
->
|
|
list__index1_det(Disjs, N, Disj),
|
|
DisjAndPath = goal_and_path(Disj, DisjPath),
|
|
make_primitive_list(Store, [DisjAndPath | GoalPaths],
|
|
ContourTail, MaybeEnd, ArgNum, HeadVars, Var,
|
|
Primitives0, Primitives)
|
|
;
|
|
error("make_primitive_list: mismatch on disj")
|
|
)
|
|
;
|
|
Goal = switch_rep(Arms),
|
|
(
|
|
Contour = [_ - ContourHeadNode | ContourTail],
|
|
ContourHeadNode = switch(_, ArmPathStr),
|
|
path_from_string_det(ArmPathStr, ArmPath),
|
|
list__append(Path, PathTail, ArmPath),
|
|
PathTail = [switch(N)]
|
|
->
|
|
list__index1_det(Arms, N, Arm),
|
|
ArmAndPath = goal_and_path(Arm, ArmPath),
|
|
make_primitive_list(Store, [ArmAndPath | GoalPaths],
|
|
ContourTail, MaybeEnd, ArgNum, HeadVars, Var,
|
|
Primitives0, Primitives)
|
|
;
|
|
error("make_primitive_list: mismatch on switch")
|
|
)
|
|
;
|
|
Goal = ite_rep(Cond, Then, Else),
|
|
(
|
|
Contour = [_ - ContourHeadNode | ContourTail],
|
|
ContourHeadNode = cond(_, CondPathStr, _),
|
|
path_from_string_det(CondPathStr, CondPath),
|
|
list__append(Path, PathTail, CondPath),
|
|
PathTail = [ite_cond]
|
|
->
|
|
ThenPath = list__append(Path, [ite_then]),
|
|
CondAndPath = goal_and_path(Cond, CondPath),
|
|
ThenAndPath = goal_and_path(Then, ThenPath),
|
|
make_primitive_list(Store,
|
|
[CondAndPath, ThenAndPath | GoalPaths],
|
|
ContourTail, MaybeEnd, ArgNum, HeadVars, Var,
|
|
Primitives0, Primitives)
|
|
;
|
|
Contour = [_ - ContourHeadNode | ContourTail],
|
|
ContourHeadNode = else(_, ElseCondId),
|
|
cond_node_from_id(Store, ElseCondId, CondNode),
|
|
CondNode = cond(_, CondPathStr, _),
|
|
path_from_string_det(CondPathStr, CondPath),
|
|
list__append(Path, PathTail, CondPath),
|
|
PathTail = [ite_cond]
|
|
->
|
|
ElsePath = list__append(Path, [ite_else]),
|
|
ElseAndPath = goal_and_path(Else, ElsePath),
|
|
make_primitive_list(Store, [ElseAndPath | GoalPaths],
|
|
ContourTail, MaybeEnd, ArgNum, HeadVars, Var,
|
|
Primitives0, Primitives)
|
|
;
|
|
error("make_primitive_list: mismatch on if-then-else")
|
|
)
|
|
;
|
|
Goal = negation_rep(NegGoal),
|
|
(
|
|
Contour = [_ - ContourHeadNode | ContourTail],
|
|
ContourHeadNode = neg_succ(_, _)
|
|
->
|
|
% The negated goal cannot contribute any bindings.
|
|
make_primitive_list(Store, GoalPaths,
|
|
ContourTail, MaybeEnd, ArgNum, HeadVars, Var,
|
|
Primitives0, Primitives)
|
|
;
|
|
Contour = [_ - ContourHeadNode | ContourTail],
|
|
ContourHeadNode = neg(_, _, _)
|
|
->
|
|
% The end of the primitive list is somewhere inside
|
|
% NegGoal.
|
|
NegPath = list__append(Path, [neg]),
|
|
NegAndPath = goal_and_path(NegGoal, NegPath),
|
|
make_primitive_list(Store, [NegAndPath],
|
|
ContourTail, MaybeEnd, ArgNum, HeadVars, Var,
|
|
Primitives0, Primitives)
|
|
;
|
|
error("make_primitive_list: mismatch on negation")
|
|
)
|
|
;
|
|
Goal = some_rep(InnerGoal, MaybeCut),
|
|
InnerPath = list__append(Path, [exist(MaybeCut)]),
|
|
InnerAndPath = goal_and_path(InnerGoal, InnerPath),
|
|
make_primitive_list(Store, [InnerAndPath | GoalPaths],
|
|
Contour, MaybeEnd, ArgNum, HeadVars, Var,
|
|
Primitives0, Primitives)
|
|
;
|
|
Goal = atomic_goal_rep(_, File, Line, BoundVars, AtomicGoal),
|
|
GeneratesEvent = atomic_goal_generates_event(AtomicGoal),
|
|
(
|
|
GeneratesEvent = yes(Args),
|
|
(
|
|
Contour = [ContourHeadId - ContourHeadNode
|
|
| ContourTail],
|
|
CallId = ContourHeadNode ^ exit_call,
|
|
call_node_from_id(Store, CallId, CallNode),
|
|
CallPathStr = CallNode ^ call_goal_path,
|
|
path_from_string_det(CallPathStr, CallPath),
|
|
CallPath = Path,
|
|
\+ (
|
|
MaybeEnd = yes(EndPath),
|
|
EndPath = Path
|
|
)
|
|
->
|
|
Primitive = primitive(File, Line, BoundVars,
|
|
AtomicGoal, Path, yes(ContourHeadId)),
|
|
Primitives1 = [Primitive | Primitives0],
|
|
make_primitive_list(Store, GoalPaths,
|
|
ContourTail, MaybeEnd, ArgNum,
|
|
HeadVars, Var, Primitives1, Primitives)
|
|
;
|
|
Contour = [_ContourHeadId - ContourHeadNode],
|
|
CallPathStr = ContourHeadNode ^ call_goal_path,
|
|
path_from_string_det(CallPathStr, CallPath),
|
|
CallPath = Path,
|
|
MaybeEnd = yes(EndPath),
|
|
EndPath = Path
|
|
->
|
|
list__index1_det(Args, ArgNum, Var),
|
|
Primitives = Primitives0
|
|
;
|
|
error("make_primitive_list: mismatch on call")
|
|
)
|
|
;
|
|
GeneratesEvent = no,
|
|
Primitive = primitive(File, Line, BoundVars,
|
|
AtomicGoal, Path, no),
|
|
Primitives1 = [Primitive | Primitives0],
|
|
make_primitive_list(Store, GoalPaths,
|
|
Contour, MaybeEnd, ArgNum, HeadVars, Var,
|
|
Primitives1, Primitives)
|
|
)
|
|
).
|
|
make_primitive_list(_, [], Contour, MaybeEnd, ArgNum, HeadVars, Var,
|
|
Primitives, Primitives) :-
|
|
require(unify(Contour, []),
|
|
"make_primitive_list: nonempty contour at end"),
|
|
require(unify(MaybeEnd, no),
|
|
"make_primitive_list: found end when looking for call"),
|
|
list__index1_det(HeadVars, ArgNum, Var).
|
|
|
|
:- pred traverse_primitives(list(annotated_primitive(R))::in,
|
|
var_rep::in, term_path::in, S::in, proc_rep::in,
|
|
subterm_origin(edt_node(R))::out) is det <= annotated_trace(S, R).
|
|
|
|
traverse_primitives([], Var0, TermPath0, _, ProcRep, Origin) :-
|
|
ProcRep = proc_rep(HeadVars, _),
|
|
ArgPos = find_arg_pos(HeadVars, Var0),
|
|
Origin = input(ArgPos, TermPath0).
|
|
traverse_primitives([Prim | Prims], Var0, TermPath0, Store, ProcRep,
|
|
Origin) :-
|
|
Prim = primitive(File, Line, BoundVars, AtomicGoal, _GoalPath,
|
|
MaybeNodeId),
|
|
(
|
|
AtomicGoal = unify_construct_rep(_CellVar, _Cons, FieldVars),
|
|
( list__member(Var0, BoundVars) ->
|
|
(
|
|
TermPath0 = [],
|
|
Origin = primitive_op(File, Line)
|
|
;
|
|
TermPath0 = [TermPathStep0 | TermPath],
|
|
list__index1_det(FieldVars, TermPathStep0,
|
|
Var),
|
|
traverse_primitives(Prims, Var, TermPath,
|
|
Store, ProcRep, Origin)
|
|
)
|
|
;
|
|
traverse_primitives(Prims, Var0, TermPath0,
|
|
Store, ProcRep, Origin)
|
|
)
|
|
;
|
|
AtomicGoal = unify_deconstruct_rep(CellVar, _Cons, FieldVars),
|
|
( list__member(Var0, BoundVars) ->
|
|
( list__nth_member_search(FieldVars, Var0, Pos) ->
|
|
traverse_primitives(Prims,
|
|
CellVar, [Pos | TermPath0],
|
|
Store, ProcRep, Origin)
|
|
;
|
|
error("traverse_primitives: bad deconstruct")
|
|
)
|
|
;
|
|
traverse_primitives(Prims, Var0, TermPath0,
|
|
Store, ProcRep, Origin)
|
|
)
|
|
;
|
|
AtomicGoal = unify_assign_rep(ToVar, FromVar),
|
|
( list__member(Var0, BoundVars) ->
|
|
require(unify(Var0, ToVar),
|
|
"traverse_primitives: bad assign"),
|
|
traverse_primitives(Prims, FromVar, TermPath0,
|
|
Store, ProcRep, Origin)
|
|
;
|
|
traverse_primitives(Prims, Var0, TermPath0,
|
|
Store, ProcRep, Origin)
|
|
)
|
|
;
|
|
AtomicGoal = pragma_foreign_code_rep(_Args),
|
|
( list__member(Var0, BoundVars) ->
|
|
Origin = primitive_op(File, Line)
|
|
;
|
|
traverse_primitives(Prims, Var0, TermPath0,
|
|
Store, ProcRep, Origin)
|
|
)
|
|
;
|
|
AtomicGoal = unify_simple_test_rep(_LVar, _RVar),
|
|
( list__member(Var0, BoundVars) ->
|
|
error("traverse_primitives: bad test")
|
|
;
|
|
traverse_primitives(Prims, Var0, TermPath0,
|
|
Store, ProcRep, Origin)
|
|
)
|
|
;
|
|
AtomicGoal = higher_order_call_rep(_, Args),
|
|
traverse_call(BoundVars, no, Args, MaybeNodeId, Prims,
|
|
Var0, TermPath0, Store, ProcRep, Origin)
|
|
;
|
|
AtomicGoal = method_call_rep(_, _, Args),
|
|
traverse_call(BoundVars, no, Args, MaybeNodeId, Prims,
|
|
Var0, TermPath0, Store, ProcRep, Origin)
|
|
;
|
|
AtomicGoal = plain_call_rep(ModuleName, PredName, Args),
|
|
PlainCallInfo = plain_call_info(File, Line,
|
|
ModuleName, PredName),
|
|
traverse_call(BoundVars, yes(PlainCallInfo), Args, MaybeNodeId,
|
|
Prims, Var0, TermPath0, Store, ProcRep, Origin)
|
|
).
|
|
|
|
:- type plain_call_info
|
|
---> plain_call_info(
|
|
file_name :: string,
|
|
line_number :: int,
|
|
module_name :: string,
|
|
pred_name :: string
|
|
).
|
|
|
|
:- pred traverse_call(list(var_rep)::in, maybe(plain_call_info)::in,
|
|
list(var_rep)::in, maybe(R)::in,
|
|
list(annotated_primitive(R))::in, var_rep::in, term_path::in,
|
|
S::in, proc_rep::in, subterm_origin(edt_node(R))::out) is det
|
|
<= annotated_trace(S, R).
|
|
|
|
traverse_call(BoundVars, MaybePlainCallInfo, Args, MaybeNodeId,
|
|
Prims, Var, TermPath, Store, ProcRep, Origin) :-
|
|
( list__member(Var, BoundVars) ->
|
|
Pos = find_arg_pos(Args, Var),
|
|
(
|
|
MaybeNodeId = yes(NodeId),
|
|
Origin = output(dynamic(NodeId), Pos, TermPath)
|
|
;
|
|
MaybeNodeId = no,
|
|
(
|
|
MaybePlainCallInfo = yes(PlainCallInfo),
|
|
PlainCallInfo = plain_call_info(File, Line,
|
|
ModuleName, PredName),
|
|
call_is_primitive(ModuleName, PredName)
|
|
->
|
|
Origin = primitive_op(File, Line)
|
|
;
|
|
error("traverse_call: no node id")
|
|
)
|
|
)
|
|
;
|
|
traverse_primitives(Prims, Var, TermPath, Store, ProcRep,
|
|
Origin)
|
|
).
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
|
|
:- pred add_paths_to_conjuncts(list(goal_rep)::in, goal_path::in, int::in,
|
|
goal_and_path_list::out) is det.
|
|
|
|
add_paths_to_conjuncts([], _, _, []).
|
|
add_paths_to_conjuncts([Goal | Goals], ParentPath, N,
|
|
[goal_and_path(Goal, Path) | GoalAndPaths]) :-
|
|
list__append(ParentPath, [conj(N)], Path),
|
|
add_paths_to_conjuncts(Goals, ParentPath, N + 1, GoalAndPaths).
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
|
|
:- func start_loc_to_subterm_mode(start_loc(R)) = subterm_mode.
|
|
|
|
start_loc_to_subterm_mode(cur_goal) = subterm_out.
|
|
start_loc_to_subterm_mode(parent_goal(_, _)) = subterm_in.
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
|
|
:- func find_arg_pos(list(var_rep), var_rep) = arg_pos.
|
|
|
|
find_arg_pos(HeadVars, Var) = ArgPos :-
|
|
find_arg_pos_2(HeadVars, Var, 1, ArgPos).
|
|
|
|
:- pred find_arg_pos_2(list(var_rep)::in, var_rep::in, int::in, arg_pos::out)
|
|
is det.
|
|
|
|
find_arg_pos_2([], _, _, _) :-
|
|
error("find_arg_pos_2: empty list").
|
|
find_arg_pos_2([HeadVar | HeadVars], Var, Pos, ArgPos) :-
|
|
( HeadVar = Var ->
|
|
ArgPos = any_head_var(Pos)
|
|
;
|
|
find_arg_pos_2(HeadVars, Var, Pos + 1, ArgPos)
|
|
).
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
|
|
:- 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_return_node_from_id(Store, Ref, Node),
|
|
(
|
|
Node = exit(_, Call, _, _, Event, _)
|
|
;
|
|
Node = fail(_, Call, _, Event)
|
|
;
|
|
Node = excp(_, Call, _, _, Event)
|
|
),
|
|
call_node_from_id(Store, Call, CallNode),
|
|
SeqNo = CallNode ^ call_seq.
|
|
|
|
:- inst edt_return_node =
|
|
bound( exit(ground, ground, ground, ground, ground, ground)
|
|
; fail(ground, ground, ground, ground)
|
|
; excp(ground, ground, ground, ground, ground)).
|
|
|
|
:- pred det_edt_return_node_from_id(S::in, R::in,
|
|
trace_node(R)::out(edt_return_node)) is det <= annotated_trace(S, R).
|
|
|
|
det_edt_return_node_from_id(Store, Ref, Node) :-
|
|
(
|
|
trace_node_from_id(Store, Ref, Node0),
|
|
(
|
|
Node0 = exit(_, _, _, _, _, _)
|
|
;
|
|
Node0 = fail(_, _, _, _)
|
|
;
|
|
Node0 = excp(_, _, _, _, _)
|
|
)
|
|
->
|
|
Node = Node0
|
|
;
|
|
error("det_edt_return_node_from_id: not a return 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, _) :-
|
|
select_arg_at_pos(ArgPos, Args, ArgInfo),
|
|
ArgInfo = arg_info(_, _, MaybeArg),
|
|
MaybeArg = yes(_).
|
|
|
|
:- 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).
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
|
|
:- pred write_origin(wrap(S)::in, subterm_origin(edt_node(R))::in,
|
|
io__state::di, io__state::uo) is det <= annotated_trace(S, R).
|
|
|
|
write_origin(wrap(Store), Origin) -->
|
|
( { Origin = output(dynamic(NodeId), ArgPos, TermPath) } ->
|
|
{ exit_node_from_id(Store, NodeId, ExitNode) },
|
|
{ ProcName = ExitNode ^ exit_atom ^ proc_name },
|
|
io__write_string("output("),
|
|
io__write_string(ProcName),
|
|
io__write_string(", "),
|
|
io__write(ArgPos),
|
|
io__write_string(", "),
|
|
io__write(TermPath),
|
|
io__write_string(")")
|
|
;
|
|
io__write(Origin)
|
|
).
|
|
|
|
:- pragma foreign_code("C",
|
|
"
|
|
|
|
/*
|
|
** The declarative debugger will print diagnostic information about the origins
|
|
** computed by dependency tracking if this flag has a positive value.
|
|
*/
|
|
|
|
int MR_DD_debug_origin = 0;
|
|
|
|
").
|
|
|
|
:- pragma foreign_decl("C",
|
|
"
|
|
extern int MR_DD_debug_origin;
|
|
").
|
|
|
|
:- pred debug_origin(int::out, io__state::di, io__state::uo) is det.
|
|
|
|
:- pragma foreign_proc("C",
|
|
debug_origin(Flag::out, IO0::di, IO::uo),
|
|
[will_not_call_mercury, promise_pure],
|
|
"
|
|
Flag = MR_DD_debug_origin;
|
|
IO = IO0;
|
|
").
|