Merge pull request #2817 from adri326/fix-2815-run_module_predicate-backtrack

Fix backtracking on the topmost predicate triggering UB in run_module_predicate
This commit is contained in:
Mark Thom
2025-02-16 22:47:49 -08:00
committed by GitHub
3 changed files with 42 additions and 1 deletions

View File

@@ -568,7 +568,7 @@ impl Machine {
self.run_module_predicate(atom!("loader"), (atom!("consult_stream"), 2)); self.run_module_predicate(atom!("loader"), (atom!("consult_stream"), 2));
} }
fn allocate_stub_choice_point(&mut self) { pub(crate) fn allocate_stub_choice_point(&mut self) {
// NOTE: create a choice point to terminate the dispatch_loop // NOTE: create a choice point to terminate the dispatch_loop
// if an exception is thrown. // if an exception is thrown.

View File

@@ -271,6 +271,11 @@ impl Machine {
.unwrap() .unwrap()
} }
/// Runs the predicate `key` in `module_name` until completion.
/// Siltently ignores failure, thrown errors and choice points.
///
/// Consider using [`Machine::run_query`] if you wish to handle
/// predicates that may fail, leave a choice point or throw.
pub(crate) fn run_module_predicate( pub(crate) fn run_module_predicate(
&mut self, &mut self,
module_name: Atom, module_name: Atom,
@@ -279,6 +284,8 @@ impl Machine {
if let Some(module) = self.indices.modules.get(&module_name) { if let Some(module) = self.indices.modules.get(&module_name) {
if let Some(code_index) = module.code_dir.get(&key) { if let Some(code_index) = module.code_dir.get(&key) {
let p = code_index.local().unwrap(); let p = code_index.local().unwrap();
// Leave a halting choice point to backtrack to in case the predicate fails or throws.
self.allocate_stub_choice_point();
self.machine_st.cp = BREAK_FROM_DISPATCH_LOOP_LOC; self.machine_st.cp = BREAK_FROM_DISPATCH_LOOP_LOC;
self.machine_st.p = p; self.machine_st.p = p;
@@ -1248,3 +1255,24 @@ impl Machine {
} }
} }
} }
#[cfg(test)]
mod tests {
use super::config::*;
use super::*;
#[test]
#[cfg_attr(miri, ignore)]
fn test_run_module_predicate_throw() {
let mut machine = MachineBuilder::default()
.with_toplevel(
r#"
:- module('$toplevel', []).
repl :- throw(kaboom).
"#,
)
.build();
machine.run_module_predicate(atom!("$toplevel"), (atom!("repl"), 0));
}
}

View File

@@ -29,6 +29,19 @@ load_scryerrc :-
). ).
'$repl' :- '$repl' :-
catch(
start_repl,
_,
% Something bad enough happened that the REPL itself threw an error.
% This can be caused by a broken user_output stream, so we cannot
% print an error.
%
% The best we can do now is halt with an error code,
% so that users can try to diagnose the issue:
halt(99)
).
start_repl :-
asserta('$toplevel':started), asserta('$toplevel':started),
raw_argv(Args0), raw_argv(Args0),
( append(Args1, ["--"|_], Args0) -> ( append(Args1, ["--"|_], Args0) ->