%---------------------------------------------------------------------------% % vim: ft=mercury ts=4 sw=4 et %---------------------------------------------------------------------------% % Copyright (C) 1999-2007, 2011 The University of Melbourne. % Copyright (C) 2014-2015, 2017-2018 The Mercury team. % This file is distributed under the terms specified in COPYING.LIB. %---------------------------------------------------------------------------% % % 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 a given EDT % to diagnose a bug. % % Because Mercury modules can be compiled with different levels of tracing, % the trace sequences generated by the back end, and passed to the front end % as "annotated traces", can include or exclude certain types of events. % This front end is able to cope with some variation in the trace events % produced, but there are some basic requirements on trace sequences % which the back end must meet: % % 1) if there are any events from a certain class (e.g. interface % events, negation events, disj events) then we require all events % of that class; % % 2) if there are any disj events, we require all negation events % and if-then-else events. % % 3) the sub-term dependency tracking algorithm requires the proc % representation and all the internal events for any call through % which it must track a sub-term. Child interface events however % may be omitted (as long as each CALL which is present has all its % corresponding REDOs, EXIT, FAIL or EXCP event(s) and vice versa). % % The backend will only build a portion of the annotated trace at a time % (down to a specified depth limit). The front end can request that more % of the annotated trace be built so it can be analysed. The front end can % either request that the subtree rooted at a particular node whose children % have not been materialized be built (down to a certain depth limit), or that % nodes above the topmost materialized node be materialized. In the first case % the require_subtree response is sent to the backend and in the latter case % the require_supertree response is sent to the backend. We use the term % "supertree" to mean a tree which strictly contains the currently materialized % portion of the annotated trace, although the backend will not materialize % nodes which already exist in the current annotated trace when materializing % a supertree. % %---------------------------------------------------------------------------% %---------------------------------------------------------------------------% :- module mdb.declarative_debugger. :- interface. :- import_module mdb.browser_info. :- import_module mdb.declarative_analyser. :- import_module mdb.declarative_execution. :- import_module mdb.declarative_tree. :- import_module mdb.help. :- import_module mdb.io_action. :- import_module mdb.term_rep. :- import_module mdbcomp. :- import_module mdbcomp.program_representation. :- import_module io. :- import_module list. :- import_module maybe. :- import_module unit. %---------------------------------------------------------------------------% % This type represents the possible truth values for nodes % in the EDT. % :- type decl_truth ---> truth_correct ; truth_erroneous ; truth_inadmissible. % 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 ---> e_bug(decl_e_bug) % An EDT whose root node is incorrect, % but whose children are all correct. ; i_bug(decl_i_bug). % An EDT whose root node is incorrect, and which has no incorrect % children but at least one inadmissible one. :- type decl_e_bug ---> incorrect_contour( % The head of the clause, in its initial state of % instantiation. init_decl_atom, % The head of the clause, in its final state of instantiation. final_decl_atom, % The path taken through the body. decl_contour, % The exit event. event_number ) ; partially_uncovered_atom( % The called atom, in its initial state. init_decl_atom, % The fail event. event_number ) ; unhandled_exception( % The called atom, in its initial state. init_decl_atom, % The exception thrown. decl_exception, % The excp event. event_number ). :- type decl_i_bug ---> inadmissible_call( % The parent atom, in its initial state. init_decl_atom, % The location of the call in the parent body. decl_position, % The inadmissible child, in its initial state. init_decl_atom, % The call event. event_number ). :- type decl_contour == list(final_decl_atom). % XXX not yet implemented. % :- 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) ---> wrong_answer(T, init_decl_atom, final_decl_atom) % 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 (i.e. % at the EXIT event). ; missing_answer(T, init_decl_atom, list(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 (i.e. % at the CALL event), and the third argument is the list % of solutions. ; unexpected_exception(T, init_decl_atom, decl_exception). % 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. :- type decl_answer(T) ---> truth_value(T, decl_truth) % The oracle knows the truth value of this node. ; suspicious_subterm(T, arg_pos, term_path, how_track_subterm, should_assert_invalid) % The oracle does not say anything about the truth value, % but is suspicious of the subterm at the given term_path % and arg_pos. ; ignore(T) % This node should be ignored. It cannot contain a bug % but its children may or may not contain a bug. ; skip(T). % The oracle has deferred answering this question. % Answers that are known by the oracle without having to consult the % user, such as answers stored in the knowledge base or answers about % trusted predicates. mdb.declarative_oracle.answer_known/3 returns % answers of this subtype. % :- inst known_answer for decl_answer/1 ---> truth_value(ground, ground) ; ignore(ground). % The evidence that a certain node is a bug. This consists of the % smallest set of questions whose answers are sufficient to % diagnose that bug. % :- type decl_evidence(T) == list(decl_question(T)). % Extract the EDT node from a question. % :- func get_decl_question_node(decl_question(T)) = T. % Get the atom the question relates to. % :- func get_decl_question_atom(decl_question(_)) = trace_atom. :- 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 :: maybe(io_action_range) ). :- pred unravel_decl_atom(some_decl_atom::in, trace_atom::out, maybe(io_action_range)::out) is det. :- type decl_exception == term_rep. % The diagnoser eventually responds with a value of this type % after it is called. % :- type diagnoser_response(R) ---> bug_found(event_number) % There was a bug found and confirmed. The event number % is for a call port (inadmissible call), an exit port % (incorrect contour), a fail port (partially uncovered atom), % or an exception port (unhandled exception). ; symptom_found(event_number) % There was another symptom of incorrect behaviour found; % this symptom will be closer, in a sense, to the location of % a bug. ; no_bug_found % There was no symptom found, or the diagnoser aborted % before finding a bug. ; require_subtree( % 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_final_event :: event_number, require_subtree_seqno :: sequence_number, % The node preceding the call node. This is needed so the % root of the new tree has the correct preceding node. require_subtree_call_preceding_node :: R, % The maximum depth to build the new subtree to. require_subtree_max_depth :: int ) ; require_supertree(event_number, sequence_number). % The analyser requires events before and after the current set % of materialized events to be generated. The given event should be % the topmost final event of the currently materialized portion % of the EDT. :- type diagnoser_state(R). % diagnoser_state_init(InputStream, OutputStream, Browser, % HelpSystem, Diagnoser): % % Initialise a new diagnoser with the given properties. % :- pred diagnoser_state_init(io.text_input_stream::in, io.text_output_stream::in, browser_info.browser_persistent_state::in, help_system::in, diagnoser_state(R)::out) is det. :- pred diagnosis(S::in, analysis_type(edt_node(R))::in, diagnoser_response(R)::out, diagnoser_state(R)::in, diagnoser_state(R)::out, browser_info.browser_persistent_state::in, browser_info.browser_persistent_state::out, io::di, io::uo) is cc_multi <= annotated_trace(S, R). %---------------------------------------------------------------------------% % The diagnoser generates exceptions of the following type. % :- type diagnoser_exception ---> internal_error( string, % predicate/function name string % error message ) ; io_error( string, % predicate/function name string % error message ) ; unimplemented_feature( string % feature that is NYI ). %---------------------------------------------------------------------------% %---------------------------------------------------------------------------% :- implementation. :- import_module mdb.declarative_edt. :- import_module mdb.declarative_oracle. :- import_module mdb.declarative_user. :- import_module mdb.util. :- import_module mdbcomp.rtti_access. :- import_module mdbcomp.sym_name. :- import_module bool. :- import_module exception. :- import_module int. :- import_module string. :- import_module univ. %---------------------------------------------------------------------------% get_decl_question_node(wrong_answer(Node, _, _)) = Node. get_decl_question_node(missing_answer(Node, _, _)) = Node. get_decl_question_node(unexpected_exception(Node, _, _)) = Node. get_decl_question_atom(wrong_answer(_, _, final_decl_atom(Atom, _))) = Atom. get_decl_question_atom(missing_answer(_, init_decl_atom(Atom), _)) = Atom. get_decl_question_atom(unexpected_exception(_, init_decl_atom(Atom), _)) = Atom. %---------------------------------------------------------------------------% unravel_decl_atom(DeclAtom, TraceAtom, MaybeIoActions) :- ( DeclAtom = init(init_decl_atom(TraceAtom)), MaybeIoActions = no ; DeclAtom = final(final_decl_atom(TraceAtom, MaybeIoActions)) ). %---------------------------------------------------------------------------% :- type diagnoser_state(R) ---> diagnoser( analyser_state :: analyser_state(edt_node(R)), oracle_state :: oracle_state, % This field keeps track of whether we should warn the user % when a supertree is requested. % We issue a warning when there have been no interactions % with the user and a supertree has been requested. % This can happen when all the nodes under the starting node % are trusted. This behaviour can be confusing, so we print % a message to explain what is going on. warn_if_searching_supertree :: bool, % The diagnoser state before the previous oracle answer % (if the oracle has given any answers yet). previous_diagnoser :: maybe(diagnoser_state(R)) ). diagnoser_state_init(InputStream, OutputStream, Browser, HelpSystem, Diagnoser) :- analyser_state_init(Analyser), oracle_state_init(InputStream, OutputStream, Browser, HelpSystem, Oracle), Diagnoser = diagnoser(Analyser, Oracle, yes, no). :- pred push_diagnoser(diagnoser_state(R)::in, diagnoser_state(R)::out) is det. push_diagnoser(!Diagnoser) :- !Diagnoser ^ previous_diagnoser := yes(!.Diagnoser). :- pred pop_diagnoser(diagnoser_state(R)::in, diagnoser_state(R)::out) is semidet. pop_diagnoser(!Diagnoser) :- LatestOracle = !.Diagnoser ^ oracle_state, !.Diagnoser ^ previous_diagnoser = yes(!:Diagnoser), LastPushedOracle = !.Diagnoser ^ oracle_state, update_revised_knowledge_base(LastPushedOracle, LatestOracle, Oracle), !Diagnoser ^ oracle_state := Oracle. diagnosis(Store, AnalysisType, Response, !Diagnoser, !Browser, !IO) :- Oracle0 = !.Diagnoser ^ oracle_state, set_oracle_browser_state(!.Browser, Oracle0, Oracle1), !Diagnoser ^ oracle_state := Oracle1, try_io(diagnosis_2(Store, AnalysisType, !.Diagnoser), Result, !IO), ( Result = succeeded({Response, !:Diagnoser}) ; Result = exception(UnivException), ( if univ_to_type(UnivException, DiagnoserException) then handle_diagnoser_exception(DiagnoserException, Response, !Diagnoser, !IO) else rethrow(Result) ) ), Oracle = !.Diagnoser ^ oracle_state, !:Browser = get_oracle_browser_state(Oracle). :- pred diagnosis_2(S::in, analysis_type(edt_node(R))::in, diagnoser_state(R)::in, {diagnoser_response(R), diagnoser_state(R)}::out, io::di, io::uo) is cc_multi <= annotated_trace(S, R). diagnosis_2(Store, AnalysisType, Diagnoser0, {Response, Diagnoser}, !IO) :- Oracle0 = Diagnoser0 ^ oracle_state, Analyser0 = Diagnoser0 ^ analyser_state, start_or_resume_analysis(wrap(Store), Oracle0, AnalysisType, AnalyserResponse, Analyser0, Analyser), Diagnoser1 = Diagnoser0 ^ analyser_state := Analyser, debug_analyser_state(Analyser, MaybeOrigin), handle_analyser_response(Store, AnalyserResponse, MaybeOrigin, Response, Diagnoser1, Diagnoser, !IO). :- pred handle_analyser_response(S::in, analyser_response(edt_node(R))::in, maybe(subterm_origin(edt_node(R)))::in, diagnoser_response(R)::out, diagnoser_state(R)::in, diagnoser_state(R)::out, io::di, io::uo) is cc_multi <= annotated_trace(S, R). handle_analyser_response(Store, AnalyserResponse, MaybeOrigin, DiagnoserResponse, !Diagnoser, !IO) :- ( AnalyserResponse = analyser_response_no_suspects, DiagnoserResponse = no_bug_found, OutputStream = get_oracle_user_output_stream(!.Diagnoser ^ oracle_state), io.write_string(OutputStream, "No bug found.\n", !IO) ; AnalyserResponse = analyser_response_bug_found(Bug, Evidence), confirm_bug(Store, Bug, Evidence, DiagnoserResponse, !Diagnoser, !IO) ; AnalyserResponse = analyser_response_oracle_question(Question), Oracle0 = !.Diagnoser ^ oracle_state, debug_origin(Flag, !IO), ( if MaybeOrigin = yes(Origin), Flag > 0 then OutputStream = get_oracle_user_output_stream(Oracle0), io.write_string(OutputStream, "Origin: ", !IO), write_origin(OutputStream, wrap(Store), Origin, !IO), io.nl(OutputStream, !IO) else true ), query_oracle(Question, OracleResponse, FromUser, Oracle0, Oracle, !IO), ( FromUser = yes, !Diagnoser ^ warn_if_searching_supertree := no, ( if oracle_response_undoable(OracleResponse) then push_diagnoser(!Diagnoser) else true ) ; FromUser = no ), !Diagnoser ^ oracle_state := Oracle, handle_oracle_response(Store, OracleResponse, DiagnoserResponse, !Diagnoser, !IO) ; AnalyserResponse = analyser_response_require_explicit_subtree(Node), edt_subtree_details(Store, Node, Event, Seqno, CallPreceding), ( if trace_implicit_tree_info(wrap(Store), Node, ImplicitTreeInfo) then ImplicitTreeInfo = implicit_tree_info(IdealDepth) else throw(internal_error("handle_analyser_response", "subtree requested for node which is not an implicit root")) ), DiagnoserResponse = require_subtree(Event, Seqno, CallPreceding, IdealDepth) ; AnalyserResponse = analyser_response_require_explicit_supertree(Node), edt_subtree_details(Store, Node, Event, Seqno, _), ( !.Diagnoser ^ warn_if_searching_supertree = no, DiagnoserResponse = require_supertree(Event, Seqno) ; !.Diagnoser ^ warn_if_searching_supertree = yes, OutputStream = get_oracle_user_output_stream(!.Diagnoser ^ oracle_state), io.write_string(OutputStream, "All descendent calls are trusted.\n" ++ "Shall I continue searching in ancestor calls?\n", !IO), read_search_supertree_response(!.Diagnoser, Response, !IO), ( Response = yes, DiagnoserResponse = require_supertree(Event, Seqno) ; Response = no, io.write_string(OutputStream, "Diagnosis aborted.\n", !IO), DiagnoserResponse = no_bug_found ), % We only want to issue the warning once, so set the flag to no. !Diagnoser ^ warn_if_searching_supertree := no ) ; AnalyserResponse = analyser_response_revise(Question), Oracle0 = !.Diagnoser ^ oracle_state, revise_oracle(Question, Oracle0, Oracle), !Diagnoser ^ oracle_state := Oracle, handle_analyser_response(Store, analyser_response_oracle_question(Question), no, DiagnoserResponse, !Diagnoser, !IO) ). :- pred read_search_supertree_response(diagnoser_state(R)::in, bool::out, io::di, io::uo) is det. read_search_supertree_response(Diagnoser, Response, !IO) :- InputStream = get_oracle_user_input_stream(Diagnoser ^ oracle_state), OutputStream = get_oracle_user_output_stream(Diagnoser ^ oracle_state), Prompt = "> ", util.trace_getline(InputStream, OutputStream, Prompt, Result, !IO), ( Result = ok(Line), UpperLine = string.to_upper(Line), ( if (UpperLine = "YES" ; UpperLine = "Y") then Response = yes else if (UpperLine = "NO" ; UpperLine = "N") then Response = no else io.write_string(OutputStream, "Please answer yes or no.\n", !IO), read_search_supertree_response(Diagnoser, Response, !IO) ) ; Result = error(ErrNo), io.format(OutputStream, "Error reading input: %s. Aborting.\n", [s(io.error_message(ErrNo))], !IO), Response = no ; Result = eof, io.write_string(OutputStream, "Unexpected EOF. Aborting.\n", !IO), Response = no ). :- pred handle_oracle_response(S::in, oracle_response(edt_node(R))::in, diagnoser_response(R)::out, diagnoser_state(R)::in, diagnoser_state(R)::out, io::di, io::uo) is cc_multi <= annotated_trace(S, R). handle_oracle_response(Store, OracleResponse, DiagnoserResponse, !Diagnoser, !IO) :- ( OracleResponse = oracle_response_answer(Answer), Oracle0 = !.Diagnoser ^ oracle_state, Analyser0 = !.Diagnoser ^ analyser_state, continue_analysis(wrap(Store), Oracle0, Answer, AnalyserResponse, Analyser0, Analyser), !Diagnoser ^ analyser_state := Analyser, debug_analyser_state(Analyser, MaybeOrigin), handle_analyser_response(Store, AnalyserResponse, MaybeOrigin, DiagnoserResponse, !Diagnoser, !IO) ; OracleResponse = oracle_response_show_info(OutputStream), Analyser = !.Diagnoser ^ analyser_state, show_info(wrap(Store), OutputStream, Analyser, !IO), ( if reask_last_question(wrap(Store), Analyser, AnalyserResponse0) then AnalyserResponse = AnalyserResponse0 else throw(internal_error("handle_oracle_response", "no last question when got show_info request")) ), debug_analyser_state(Analyser, MaybeOrigin), handle_analyser_response(Store, AnalyserResponse, MaybeOrigin, DiagnoserResponse, !Diagnoser, !IO) ; OracleResponse = oracle_response_change_search(Mode), Oracle0 = !.Diagnoser ^ oracle_state, Analyser0 = !.Diagnoser ^ analyser_state, change_search_mode(wrap(Store), Oracle0, Mode, AnalyserResponse, Analyser0, Analyser), !Diagnoser ^ analyser_state := Analyser, debug_analyser_state(Analyser, MaybeOrigin), handle_analyser_response(Store, AnalyserResponse, MaybeOrigin, DiagnoserResponse, !Diagnoser, !IO) ; OracleResponse = oracle_response_undo, ( if pop_diagnoser(!.Diagnoser, PoppedDiagnoser) then !:Diagnoser = PoppedDiagnoser else OutputStream = get_oracle_user_output_stream(!.Diagnoser ^ oracle_state), io.write_string(OutputStream, "Undo stack empty.\n", !IO) ), Analyser0 = !.Diagnoser ^ analyser_state, ( if reask_last_question(wrap(Store), Analyser0, AnalyserResponse0) then AnalyserResponse = AnalyserResponse0 else throw(internal_error("handle_oracle_response", "no last question when got undo request")) ), debug_analyser_state(Analyser0, MaybeOrigin), handle_analyser_response(Store, AnalyserResponse, MaybeOrigin, DiagnoserResponse, !Diagnoser, !IO) ; OracleResponse = oracle_response_exit_diagnosis(Node), edt_subtree_details(Store, Node, Event, _, _), DiagnoserResponse = symptom_found(Event) ; OracleResponse = oracle_response_abort_diagnosis, DiagnoserResponse = no_bug_found, OutputStream = get_oracle_user_output_stream(!.Diagnoser ^ oracle_state), io.write_string(OutputStream, "Diagnosis aborted.\n", !IO) ). :- pred confirm_bug(S::in, decl_bug::in, decl_evidence(T)::in, diagnoser_response(R)::out, diagnoser_state(R)::in, diagnoser_state(R)::out, io::di, io::uo) is cc_multi <= annotated_trace(S, R). confirm_bug(Store, Bug, Evidence, Response, !Diagnoser, !IO) :- Oracle0 = !.Diagnoser ^ oracle_state, oracle_confirm_bug(Bug, Evidence, Confirmation, Oracle0, Oracle, !IO), !Diagnoser ^ oracle_state := Oracle, ( Confirmation = confirm_bug, decl_bug_get_event_number(Bug, Event), Response = bug_found(Event) ; Confirmation = overrule_bug, overrule_bug(Store, Response, !Diagnoser, !IO) ; Confirmation = abort_diagnosis, Response = no_bug_found ). :- pred overrule_bug(S::in, diagnoser_response(R)::out, diagnoser_state(R)::in, diagnoser_state(R)::out, io::di, io::uo) is cc_multi <= annotated_trace(S, R). overrule_bug(Store, Response, Diagnoser0, Diagnoser, !IO) :- Analyser0 = Diagnoser0 ^ analyser_state, revise_analysis(wrap(Store), AnalyserResponse, Analyser0, Analyser), Diagnoser1 = Diagnoser0 ^ analyser_state := Analyser, debug_analyser_state(Analyser, MaybeOrigin), handle_analyser_response(Store, AnalyserResponse, MaybeOrigin, Response, Diagnoser1, Diagnoser, !IO). %---------------------------------------------------------------------------% % Export a monomorphic version of diagnosis_state_init/4, % to make it easier to call from C code. % :- pred diagnoser_state_init_store(io.text_input_stream::in, io.text_output_stream::in, browser_info.browser_persistent_state::in, help_system::in, diagnoser_state(trace_node_id)::out) is det. :- pragma foreign_export("C", diagnoser_state_init_store(in, in, in, in, out), "MR_DD_decl_diagnosis_state_init"). diagnoser_state_init_store(InputStream, OutputStream, Browser, HelpSystem, Diagnoser) :- diagnoser_state_init(InputStream, OutputStream, Browser, HelpSystem, Diagnoser). % This is called when the user starts a new declarative debugging session % with the dd command (and the --resume option was not given). % :- pred diagnoser_session_init(diagnoser_state(trace_node_id)::in, diagnoser_state(trace_node_id)::out) is det. diagnoser_session_init(!Diagnoser) :- !Diagnoser ^ warn_if_searching_supertree := yes. :- pragma foreign_export("C", diagnoser_session_init(in, out), "MR_DD_decl_session_init"). % Set the testing flag of the user_state in the given diagnoser. % :- pred set_diagnoser_to_testing( diagnoser_state(trace_node_id)::in, diagnoser_state(trace_node_id)::out) is det. :- pragma foreign_export("C", set_diagnoser_to_testing(in, out), "MR_DD_decl_set_diagnoser_to_testing"). set_diagnoser_to_testing(!Diagnoser) :- Oracle0 = !.Diagnoser ^ oracle_state, set_oracle_testing_flag(we_are_testing, Oracle0, Oracle), !Diagnoser ^ oracle_state := Oracle. % Set the testing flag of the user_state in the given diagnoser. % :- pred set_diagnoser_to_not_testing( diagnoser_state(trace_node_id)::in, diagnoser_state(trace_node_id)::out) is det. :- pragma foreign_export("C", set_diagnoser_to_not_testing(in, out), "MR_DD_decl_set_diagnoser_to_not_testing"). set_diagnoser_to_not_testing(!Diagnoser) :- Oracle0 = !.Diagnoser ^ oracle_state, set_oracle_testing_flag(we_are_not_testing, Oracle0, Oracle), !Diagnoser ^ oracle_state := Oracle. :- pred set_fallback_search_mode(trace_node_store::in, mdb.declarative_analyser.search_mode::in, diagnoser_state(trace_node_id)::in, diagnoser_state(trace_node_id)::out) is det. :- pragma foreign_export("C", mdb.declarative_debugger.set_fallback_search_mode(in, in, in, out), "MR_DD_decl_set_fallback_search_mode"). set_fallback_search_mode(Store, SearchMode, !Diagnoser) :- Analyser0 = !.Diagnoser ^ analyser_state, set_analyser_fallback_search_mode(wrap(Store), SearchMode, Analyser0, Analyser), !Diagnoser ^ analyser_state := Analyser. :- pred reset_knowledge_base( diagnoser_state(trace_node_id)::in, diagnoser_state(trace_node_id)::out) is det. :- pragma foreign_export("C", mdb.declarative_debugger.reset_knowledge_base(in, out), "MR_DD_decl_reset_knowledge_base"). reset_knowledge_base(!Diagnoser) :- Oracle0 = !.Diagnoser ^ oracle_state, reset_oracle_knowledge_base(Oracle0, Oracle), !Diagnoser ^ oracle_state := Oracle. :- func top_down_search_mode = mdb.declarative_analyser.search_mode. top_down_search_mode = mdb.declarative_analyser.top_down_search_mode. :- pragma foreign_export("C", mdb.declarative_debugger.top_down_search_mode = out, "MR_DD_decl_top_down_search_mode"). :- func divide_and_query_search_mode = mdb.declarative_analyser.search_mode. divide_and_query_search_mode = mdb.declarative_analyser.divide_and_query_search_mode. :- pragma foreign_export("C", mdb.declarative_debugger.divide_and_query_search_mode = out, "MR_DD_decl_divide_and_query_search_mode"). :- func suspicion_divide_and_query_search_mode = mdb.declarative_analyser.search_mode. suspicion_divide_and_query_search_mode = mdb.declarative_analyser.suspicion_divide_and_query_search_mode. :- pragma foreign_export("C", mdb.declarative_debugger.suspicion_divide_and_query_search_mode = out, "MR_DD_decl_suspicion_divide_and_query_search_mode"). % Export a monomorphic version of diagnosis/10 that passes a newly % materialized tree for use with the C backend code. % :- pred diagnosis_new_tree(trace_node_store::in, trace_node_id::in, diagnoser_response(trace_node_id)::out, diagnoser_state(trace_node_id)::in, diagnoser_state(trace_node_id)::out, browser_info.browser_persistent_state::in, browser_info.browser_persistent_state::out, io::di, io::uo) is cc_multi. :- pragma foreign_export("C", diagnosis_new_tree(in, in, out, in, out, in, out, di, uo), "MR_DD_decl_diagnosis_new_tree"). diagnosis_new_tree(Store, Node, Response, !State, !Browser, !IO) :- diagnosis(Store, new_tree(dynamic(Node)), Response, !State, !Browser, !IO). % Export a monomorphic version of diagnosis/10 that requests the % continuation of a previously suspended declarative debugging session. % :- pred diagnosis_resume_previous(trace_node_store::in, diagnoser_response(trace_node_id)::out, diagnoser_state(trace_node_id)::in, diagnoser_state(trace_node_id)::out, browser_info.browser_persistent_state::in, browser_info.browser_persistent_state::out, io::di, io::uo) is cc_multi. :- pragma foreign_export("C", diagnosis_resume_previous(in, out, in, out, in, out, di, uo), "MR_DD_decl_diagnosis_resume_previous"). diagnosis_resume_previous(Store, Response, !State, !Browser, !IO) :- diagnosis(Store, resume_previous, Response, !State, !Browser, !IO). % Export some predicates so that C code can interpret the % diagnoser response. % :- pred diagnoser_bug_found(diagnoser_response(trace_node_id)::in, event_number::out) is semidet. :- pragma foreign_export("C", diagnoser_bug_found(in, out), "MR_DD_diagnoser_bug_found"). diagnoser_bug_found(bug_found(Event), Event). :- pred diagnoser_symptom_found(diagnoser_response(trace_node_id)::in, event_number::out) is semidet. :- pragma foreign_export("C", diagnoser_symptom_found(in, out), "MR_DD_diagnoser_symptom_found"). diagnoser_symptom_found(symptom_found(Event), Event). :- pred diagnoser_no_bug_found(diagnoser_response(trace_node_id)::in) is semidet. :- pragma foreign_export("C", diagnoser_no_bug_found(in), "MR_DD_diagnoser_no_bug_found"). diagnoser_no_bug_found(no_bug_found). :- pred diagnoser_require_subtree(diagnoser_response(trace_node_id)::in, event_number::out, sequence_number::out, trace_node_id::out, int::out) is semidet. :- pragma foreign_export("C", diagnoser_require_subtree(in, out, out, out, out), "MR_DD_diagnoser_require_subtree"). diagnoser_require_subtree(require_subtree(Event, SeqNo, CallPreceding, MaxDepth), Event, SeqNo, CallPreceding, MaxDepth). :- pred diagnoser_require_supertree(diagnoser_response(trace_node_id)::in, event_number::out, sequence_number::out) is semidet. :- pragma foreign_export("C", diagnoser_require_supertree(in, out, out), "MR_DD_diagnoser_require_supertree"). diagnoser_require_supertree(require_supertree(Event, SeqNo), Event, SeqNo). %---------------------------------------------------------------------------% % Adds a trusted module to the given diagnoser. % :- pred add_trusted_module(string::in, diagnoser_state(trace_node_id)::in, diagnoser_state(trace_node_id)::out) is det. :- pragma foreign_export("C", mdb.declarative_debugger.add_trusted_module(in, in, out), "MR_DD_decl_add_trusted_module"). add_trusted_module(ModuleName, !Diagnoser) :- SymModuleName = string_to_sym_name(ModuleName), Oracle0 = !.Diagnoser ^ oracle_state, add_trusted_module(SymModuleName, Oracle0, Oracle), !Diagnoser ^ oracle_state := Oracle. % Adds a trusted predicate/function to the given diagnoser. % :- pred add_trusted_pred_or_func(proc_layout::in, diagnoser_state(trace_node_id)::in, diagnoser_state(trace_node_id)::out) is det. :- pragma foreign_export("C", mdb.declarative_debugger.add_trusted_pred_or_func(in, in, out), "MR_DD_decl_add_trusted_pred_or_func"). add_trusted_pred_or_func(ProcLayout, !Diagnoser) :- Oracle0 = !.Diagnoser ^ oracle_state, add_trusted_pred_or_func(ProcLayout, Oracle0, Oracle), !Diagnoser ^ oracle_state := Oracle. :- pred trust_standard_library(diagnoser_state(trace_node_id)::in, diagnoser_state(trace_node_id)::out) is det. :- pragma foreign_export("C", mdb.declarative_debugger.trust_standard_library(in, out), "MR_DD_decl_trust_standard_library"). trust_standard_library(!Diagnoser) :- Oracle0 = !.Diagnoser ^ oracle_state, declarative_oracle.trust_standard_library(Oracle0, Oracle), !Diagnoser ^ oracle_state := Oracle. :- pred remove_trusted(int::in, diagnoser_state(trace_node_id)::in, diagnoser_state(trace_node_id)::out) is semidet. :- pragma foreign_export("C", mdb.declarative_debugger.remove_trusted(in, in, out), "MR_DD_decl_remove_trusted"). remove_trusted(N, !Diagnoser) :- Oracle0 = !.Diagnoser ^ oracle_state, remove_trusted(N, Oracle0, Oracle), !Diagnoser ^ oracle_state := Oracle. % get_trusted_list(Diagnoser, MDBCommandFormat, String): % % Return a string listing the trusted objects for Diagnoser. % If MDBCommandFormat is true, we return the list in format % that it can be run as a series of mdb `trust' commands. % Otherwise, we return them in a format suitable for display only. % :- pred get_trusted_list(diagnoser_state(trace_node_id)::in, bool::in, string::out) is det. :- pragma foreign_export("C", mdb.declarative_debugger.get_trusted_list(in, in, out), "MR_DD_decl_get_trusted_list"). get_trusted_list(Diagnoser, MDBCommandFormat, List) :- get_trusted_list(Diagnoser ^ oracle_state, MDBCommandFormat, List). %---------------------------------------------------------------------------% :- pred handle_diagnoser_exception(diagnoser_exception::in, diagnoser_response(R)::out, diagnoser_state(R)::in, diagnoser_state(R)::out, io::di, io::uo) is det. handle_diagnoser_exception(DiagnoserException, Response, !Diagnoser, !IO) :- ( DiagnoserException = internal_error(Loc, Msg), io.stderr_stream(StdErr, !IO), io.write_string(StdErr, "An internal error has occurred; " ++ "diagnosis will be aborted. Debugging\n" ++ "message follows:\n" ++ Loc ++ ": " ++ Msg ++ "\n" ++ "Please report bugs via the Mercury bug tracking system at\n" ++ " or via e-mail to " ++ "bugs@mercurylang.org.\n", !IO), % Reset the analyser, in case it was left in an inconsistent state. reset_analyser(!.Diagnoser ^ analyser_state, Analyser), !Diagnoser ^ analyser_state := Analyser, Response = no_bug_found ; DiagnoserException = io_error(Loc, Msg), io.stderr_stream(StdErr, !IO), io.write_string(StdErr, "I/O error: " ++ Loc ++ ": " ++ Msg ++ ".\n" ++ "Diagnosis will be aborted.\n", !IO), % Reset the analyser, in case it was left in an inconsistent state. reset_analyser(!.Diagnoser ^ analyser_state, Analyser), !Diagnoser ^ analyser_state := Analyser, Response = no_bug_found ; DiagnoserException = unimplemented_feature(Feature), OutputStream = get_oracle_user_output_stream(!.Diagnoser ^ oracle_state), io.write_string(OutputStream, "Sorry, the diagnosis cannot continue " ++ "because it requires support for the following: \n" ++ Feature ++ ".\n" ++ "The debugger is a work in progress, and this is not " ++ "supported in the\ncurrent version.\n", !IO), % Reset the analyser, in case it was left in an inconsistent state. reset_analyser(!.Diagnoser ^ analyser_state, Analyser), !Diagnoser ^ analyser_state := Analyser, Response = no_bug_found ). %---------------------------------------------------------------------------% :- pred decl_bug_get_event_number(decl_bug::in, event_number::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(io.text_output_stream::in, wrap(S)::in, subterm_origin(edt_node(R))::in, io::di, io::uo) is det <= annotated_trace(S, R). write_origin(Stream, wrap(Store), Origin, !IO) :- ( if Origin = origin_output(dynamic(NodeId), ArgPos, TermPath) then exit_node_from_id(Store, NodeId, ExitNode), ProcLayout = get_proc_layout_from_label_layout(ExitNode ^ exit_label), ProcLabel = get_proc_label_from_layout(ProcLayout), ProcName = get_proc_name(ProcLabel), ArgPosStr = string.string(ArgPos), TermPathStr = string.string(TermPath), io.format(Stream, "output(%s, %s, %s)", [s(ProcName), s(ArgPosStr), s(TermPathStr)], !IO) else io.write(Stream, Origin, !IO) ). :- 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::di, io::uo) is det. :- pragma no_determinism_warning(pred(debug_origin/3)). :- pragma foreign_proc("C", debug_origin(Flag::out, _IO0::di, _IO::uo), [will_not_call_mercury, promise_pure, tabled_for_io], " Flag = MR_DD_debug_origin; "). debug_origin(_, !IO) :- private_builtin.sorry("declarative_debugger.debug_origin"). %---------------------------------------------------------------------------% :- end_module mdb.declarative_debugger. %---------------------------------------------------------------------------%