mirror of
https://github.com/Mercury-Language/mercury.git
synced 2025-12-14 05:12:33 +00:00
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.
1101 lines
33 KiB
Mathematica
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).
|
|
|