scryer-prolog icon indicating copy to clipboard operation
scryer-prolog copied to clipboard

`get_n_chars/3` causes a panic when run via `:- initialization`

Open pmikkelsen opened this issue 4 months ago • 9 comments

I am trying to build a small language interpreter using scryer prolog, and as part of that, I use :- initialization(run). to automatically run my repl loop (run/0). However, if I hit the tab key while inside get_n_chars/3, scryer crashes.

A cut-down test case is this: (save it in test.pl, and run it using scryer-prolog test.pl, then hit the tab key)

:- use_module(library(charsio)).
:- initialization(run).

run :- get_n_chars(user_input, 1, _).

If I comment out the :- initialization(run) line, and evaluate run/0 manually, the tab key no longer causes a crash.

The version of scryer prolog I use is build from source today (revision 7ffd93c0), and the operating system is Linux, x86_64, in case that makes any difference.

pmikkelsen avatar Aug 21 '25 16:08 pmikkelsen

Here is a backtrace.

peter@fedora:~/src/lpa$ RUST_BACKTRACE=full scryer-prolog test.pl

thread 'main' panicked at src/repl_helper.rs:71:51:
called `Option::unwrap()` on a `None` value
stack backtrace:
   0:     0x55de5f8debd2 - std::backtrace_rs::backtrace::libunwind::trace::h77485d2069ba1ee3
                               at /builddir/build/BUILD/rust-1.89.0-build/rustc-1.89.0-src/library/std/src/../../backtrace/src/backtrace/libunwind.rs:117:9
   1:     0x55de5f8debd2 - std::backtrace_rs::backtrace::trace_unsynchronized::h3a27612f82c06f3a
                               at /builddir/build/BUILD/rust-1.89.0-build/rustc-1.89.0-src/library/std/src/../../backtrace/src/backtrace/mod.rs:66:14
   2:     0x55de5f8debd2 - std::sys::backtrace::_print_fmt::hd7ddbd4e59565ff8
                               at /builddir/build/BUILD/rust-1.89.0-build/rustc-1.89.0-src/library/std/src/sys/backtrace.rs:66:9
   3:     0x55de5f8debd2 - <std::sys::backtrace::BacktraceLock::print::DisplayBacktrace as core::fmt::Display>::fmt::h5cfe18ea6f2fdfd7
                               at /builddir/build/BUILD/rust-1.89.0-build/rustc-1.89.0-src/library/std/src/sys/backtrace.rs:39:26
   4:     0x55de5f4501b3 - core::fmt::rt::Argument::fmt::h1714e199d350f3ea
                               at /builddir/build/BUILD/rust-1.89.0-build/rustc-1.89.0-src/library/core/src/fmt/rt.rs:173:76
   5:     0x55de5f4501b3 - core::fmt::write::h7d767db0d1b2d697
                               at /builddir/build/BUILD/rust-1.89.0-build/rustc-1.89.0-src/library/core/src/fmt/mod.rs:1465:25
   6:     0x55de5f8de75f - std::io::default_write_fmt::h64f07b16a0687c70
                               at /builddir/build/BUILD/rust-1.89.0-build/rustc-1.89.0-src/library/std/src/io/mod.rs:639:11
   7:     0x55de5f8de75f - std::io::Write::write_fmt::ha40dd83d8c3eb1a3
                               at /builddir/build/BUILD/rust-1.89.0-build/rustc-1.89.0-src/library/std/src/io/mod.rs:1954:13
   8:     0x55de5f8dea33 - std::sys::backtrace::BacktraceLock::print::h92bcbc0d077a2dbc
                               at /builddir/build/BUILD/rust-1.89.0-build/rustc-1.89.0-src/library/std/src/sys/backtrace.rs:42:9
   9:     0x55de5f8de403 - std::panicking::default_hook::{{closure}}::hb6b1ab4366c73b14
  10:     0x55de5f8de403 - std::panicking::default_hook::hd656a13b4c89a3bc
                               at /builddir/build/BUILD/rust-1.89.0-build/rustc-1.89.0-src/library/std/src/panicking.rs:327:9
  11:     0x55de5f8de403 - std::panicking::rust_panic_with_hook::h29abf28177e63c84
                               at /builddir/build/BUILD/rust-1.89.0-build/rustc-1.89.0-src/library/std/src/panicking.rs:833:13
  12:     0x55de5f916ab5 - std::panicking::begin_panic_handler::{{closure}}::hcdd4955adcd749e1
                               at /builddir/build/BUILD/rust-1.89.0-build/rustc-1.89.0-src/library/std/src/panicking.rs:699:13
  13:     0x55de5f916a49 - std::sys::backtrace::__rust_end_short_backtrace::ha7685e8a98c61a2b
                               at /builddir/build/BUILD/rust-1.89.0-build/rustc-1.89.0-src/library/std/src/sys/backtrace.rs:168:18
  14:     0x55de5f917dfc - __rustc[80c2b0bdb72b0a6c]::rust_begin_unwind
                               at /builddir/build/BUILD/rust-1.89.0-build/rustc-1.89.0-src/library/std/src/panicking.rs:697:5
  15:     0x55de5f41b91f - core::panicking::panic_fmt::hc89b0bc3fafe8271
                               at /builddir/build/BUILD/rust-1.89.0-build/rustc-1.89.0-src/library/core/src/panicking.rs:75:14
  16:     0x55de5f41ba5b - core::panicking::panic::h13f78b0d410218f2
                               at /builddir/build/BUILD/rust-1.89.0-build/rustc-1.89.0-src/library/core/src/panicking.rs:145:5
  17:     0x55de5f41bc38 - core::option::unwrap_failed::h5189ee9f1cf5c131
                               at /builddir/build/BUILD/rust-1.89.0-build/rustc-1.89.0-src/library/core/src/option.rs:2072:5
  18:     0x55de5f782383 - <scryer_prolog::repl_helper::Helper as rustyline::completion::Completer>::complete::hdbaded81d79f6e27
  19:     0x55de5f782383 - rustyline::complete_line::h13259633e788d687
                               at /home/peter/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rustyline-14.0.0/src/lib.rs:90:41
  20:     0x55de5f782383 - rustyline::Editor<H,I>::readline_edit::h92f245a57d5a1e5c
                               at /home/peter/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rustyline-14.0.0/src/lib.rs:734:28
  21:     0x55de5f782383 - rustyline::Editor<H,I>::readline_with::h99dc0714fb9a0327
                               at /home/peter/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rustyline-14.0.0/src/lib.rs:668:35
  22:     0x55de5f7f0b6e - rustyline::Editor<H,I>::readline::h9a5c1b92e4c460de
                               at /home/peter/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rustyline-14.0.0/src/lib.rs:642:14
  23:     0x55de5f7f0b6e - scryer_prolog::read::ReadlineStream::call_readline::h89e458c405089538
                               at /home/peter/.cargo/git/checkouts/scryer-prolog-72624e9e4bca59c1/7ffd93c/src/read.rs:175:23
  24:     0x55de5f8548d7 - <scryer_prolog::read::ReadlineStream as scryer_prolog::parser::char_reader::CharRead>::peek_char::h9860e36660db23e9
                               at /home/peter/.cargo/git/checkouts/scryer-prolog-72624e9e4bca59c1/7ffd93c/src/read.rs:272:33
  25:     0x55de5f8548d7 - <scryer_prolog::machine::streams::Stream as scryer_prolog::parser::char_reader::CharRead>::peek_char::h2c341af224e8d165
                               at /home/peter/.cargo/git/checkouts/scryer-prolog-72624e9e4bca59c1/7ffd93c/src/machine/streams.rs:875:57
  26:     0x55de5f8cf436 - scryer_prolog::machine::streams::<impl scryer_prolog::machine::machine_state::MachineState>::open_parsing_stream::hf4b3d0867f4fb9bf
                               at /home/peter/.cargo/git/checkouts/scryer-prolog-72624e9e4bca59c1/7ffd93c/src/machine/streams.rs:1950:22
  27:     0x55de5f6902f8 - scryer_prolog::machine::system_calls::<impl scryer_prolog::machine::Machine>::get_n_chars::h76884cdafe1a6a51
                               at /home/peter/.cargo/git/checkouts/scryer-prolog-72624e9e4bca59c1/7ffd93c/src/machine/system_calls.rs:3652:44
  28:     0x55de5f6902f8 - scryer_prolog::machine::dispatch::<impl scryer_prolog::machine::Machine>::dispatch_loop::h7ebf3060d7167bc0
                               at /home/peter/.cargo/git/checkouts/scryer-prolog-72624e9e4bca59c1/7ffd93c/src/machine/dispatch.rs:3713:61
  29:     0x55de5f6902f8 - scryer_prolog::machine::Machine::run_module_predicate::hd71982fb45bb91df
                               at /home/peter/.cargo/git/checkouts/scryer-prolog-72624e9e4bca59c1/7ffd93c/src/machine/mod.rs:269:29
  30:     0x55de5f7a6c22 - scryer_prolog::run_binary::{{closure}}::h1c3e117168389653
                               at /home/peter/.cargo/git/checkouts/scryer-prolog-72624e9e4bca59c1/7ffd93c/src/lib.rs:81:13
  31:     0x55de5f7a6c22 - tokio::runtime::park::CachedParkThread::block_on::{{closure}}::he90c797f404ed0fd
                               at /home/peter/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/tokio-1.45.0/src/runtime/park.rs:284:71
  32:     0x55de5f7a6c22 - tokio::task::coop::with_budget::hb0bd1a396e119866
                               at /home/peter/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/tokio-1.45.0/src/task/coop/mod.rs:167:5
  33:     0x55de5f7a6c22 - tokio::task::coop::budget::h036feec077c1235a
                               at /home/peter/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/tokio-1.45.0/src/task/coop/mod.rs:133:5
  34:     0x55de5f7a6c22 - tokio::runtime::park::CachedParkThread::block_on::h30bf4fcb541d415a
                               at /home/peter/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/tokio-1.45.0/src/runtime/park.rs:284:31
  35:     0x55de5f7a6c22 - tokio::runtime::context::blocking::BlockingRegionGuard::block_on::h46f08a9cd1e55ada
                               at /home/peter/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/tokio-1.45.0/src/runtime/context/blocking.rs:66:14
  36:     0x55de5f7a6c22 - tokio::runtime::scheduler::multi_thread::MultiThread::block_on::{{closure}}::h2f7f1cfc899e163b
                               at /home/peter/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/tokio-1.45.0/src/runtime/scheduler/multi_thread/mod.rs:87:22
  37:     0x55de5f7a6c22 - tokio::runtime::context::runtime::enter_runtime::hedd9e060e4b53cc0
                               at /home/peter/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/tokio-1.45.0/src/runtime/context/runtime.rs:65:16
  38:     0x55de5f7a6c22 - tokio::runtime::scheduler::multi_thread::MultiThread::block_on::h9014a1cb3a2e530f
                               at /home/peter/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/tokio-1.45.0/src/runtime/scheduler/multi_thread/mod.rs:86:9
  39:     0x55de5f7a6c22 - tokio::runtime::runtime::Runtime::block_on_inner::h1c9a3c9aba5a9b02
                               at /home/peter/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/tokio-1.45.0/src/runtime/runtime.rs:358:50
  40:     0x55de5f7a6c22 - tokio::runtime::runtime::Runtime::block_on::hdb20448a65bd8356
                               at /home/peter/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/tokio-1.45.0/src/runtime/runtime.rs:330:18
  41:     0x55de5f7a6c22 - scryer_prolog::run_binary::hb9de05935ea3fa33
                               at /home/peter/.cargo/git/checkouts/scryer-prolog-72624e9e4bca59c1/7ffd93c/src/lib.rs:77:13
  42:     0x55de5f43f013 - core::ops::function::FnOnce::call_once::h7518abea803d4efc
                               at /builddir/build/BUILD/rust-1.89.0-build/rustc-1.89.0-src/library/core/src/ops/function.rs:250:5
  43:     0x55de5f43f013 - std::sys::backtrace::__rust_begin_short_backtrace::hf28ff8d951e0ae8b
                               at /builddir/build/BUILD/rust-1.89.0-build/rustc-1.89.0-src/library/std/src/sys/backtrace.rs:152:18
  44:     0x55de5f43f4b1 - main
  45:     0x7fa625924575 - __libc_start_call_main
  46:     0x7fa625924628 - __libc_start_main_impl
  47:     0x55de5f43ef45 - _start
  48:                0x0 - <unknown>

pmikkelsen avatar Aug 21 '25 16:08 pmikkelsen

As a workaround, does it work if you invoke Scryer with -g run file.pl instead of using the initialization/1 directive?

EDIT: It also crashes:

$ scryer-prolog -g run issue3057.pl 
[tab]
thread 'main' panicked at src/repl_helper.rs:71:51:
called `Option::unwrap()` on a `None` value
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

triska avatar Aug 21 '25 17:08 triska

The interesting thing to me is that the point where the crash occurs is not the "reading of input" Rust part I would expect to be invoked in this example:

https://github.com/mthom/scryer-prolog/blob/7ffd93c0d7a9521222d1e87acff17aad39e69e9e/src/repl_helper.rs#L71

This code seems to be part of the toplevel completion, i.e., something that is invoked via repl/0read_and_match/0'$read_query_term'/5, to eventually read_term_from_user_input:

https://github.com/mthom/scryer-prolog/blob/7ffd93c0d7a9521222d1e87acff17aad39e69e9e/src/machine/machine_state.rs#L744

On the toplevel interaction, I expect such completions to be taken into account.

get_n_chars/3, on the other hand, is expected to read characters without any completion. It is interesting that it works correctly when run/0 is invoked from the toplevel, but not when invoked via the -g command line option, because in both cases, the goal is invoked from toplevel.pl, in roughly the same way.

If you want to take a look at it, goals that are posted as queries on the toplevel are run here:

https://github.com/mthom/scryer-prolog/blob/7ffd93c0d7a9521222d1e87acff17aad39e69e9e/src/toplevel.pl#L258

For comparison, goals that are specified via the -g command line option are invoked here, in the same file:

https://github.com/mthom/scryer-prolog/blob/7ffd93c0d7a9521222d1e87acff17aad39e69e9e/src/toplevel.pl#L141

There may be an unintended difference between these cases, so that Scryer is in an unexpected state when running the -g goals, and thus the textual completion is unintentionally invoked when pressing TAB in this case.

I would greatly appreciate if you could take a look at this, thank you a lot!

triska avatar Aug 21 '25 18:08 triska

Thanks, I will take a look and see if I can make some sense of it.

pmikkelsen avatar Aug 21 '25 19:08 pmikkelsen

Okay, I think the general issue is that standard input is internally a ReadlineStream, and thus any attempt to read a character from that stream will enter the readline code, which is what handles tab-completion and so on.

I haven't yet found out why it crashes, but I suspect some state which is uses to provide completions, hasn't been setup yet. However, this is a red herring. The real issue is that input in get_char, peek_char, and get_n_chars (perhaps others), shouldn't have readline semantics at all in my opinion. It should just read a character from the underlying input stream directly.

Interestingly, I also ran into issue #2787 earlier today, and this seems to be caused by the same code. When the readline library (rustyline) is called to get a character, it starts by clearing the current line on standard output, and then printing a prompt, which in some cases is just an empty string, if no ?- prompt is needed. This means a call to peek_char/1 will emit a newline, which is unexpected: ?- put_char('>'), flush_output(user_output), peek_char(X). shows this behaviour.

I suspect a proper fix for this issue and #2787 will be to perform raw character reads instead of invoking the readline functionality, for the predicates where it makes sense. I will see if I am able to create a pull request for this at some point in the near future.

pmikkelsen avatar Aug 21 '25 20:08 pmikkelsen

Your PR solves this nicely!

I would also like to add: For your concrete use case, get_single_char/1 from library(charsio) may also be useful, it truly reads a single character and you do not have to press RET on the terminal.

triska avatar Aug 22 '25 16:08 triska

@pmikkelsen: I found another issue where rustyline is mentioned: https://github.com/mthom/scryer-prolog/issues/1897

Is this related, and can it be addressed analogously?

triska avatar Aug 24 '25 06:08 triska

My guess would be that instead of "blindly" opening standard input as a ReadlineStream internally, we should attempt to check if standard input is indeed connected to a terminal first, and if not, open it as another stream type internally (I am not familiar enough with the code to say which, but InputFileStream or PipeReader sound like they might be fitting). is_terminal() seems like a good and standard way to check if stdin is a terminal or not. As far as I know, other language interpreters typically do this, and then they have a flag to force interactive mode in case the autodetection is wrong.

pmikkelsen avatar Aug 24 '25 12:08 pmikkelsen

Yes indeed, PipeReader could be fitting! I would greatly appreciate if you could take a look also at this conceptually related issue in case you are interested, thank you a lot!

triska avatar Aug 24 '25 13:08 triska