LogtalkDotOrg / logtalk3

Logtalk - declarative object-oriented logic programming language
https://logtalk.org
Apache License 2.0
423 stars 31 forks source link

Non-existent object error cause infinite loop when using SWI `-t' option #39

Closed dram closed 7 years ago

dram commented 7 years ago

When using SWI -t option to execute a goal, non-existent object error will drop SWI into an infinite loop, e.g.:

% swilgt.sh -t "time::now(_, _, _)"
...
ERROR: object `time' does not exist
ERROR: object `time' does not exist
ERROR: object `time' does not exist
...

I'm using Logtalk 3.9.3-rc3 and SWI 7.2.3.

pmoura commented 7 years ago

The goal you provide with the -t does not satisfy the requirements for a top-level goal. See the SWI-Prolog documentation at http://www.swi-prolog.org/pldoc/man?section=runoptions Notably:

If the top level raises an exception, this is printed as an uncaught error and the top level is restarted.

You can prevent the infinite loop when the top level goal is restarted using e.g.

$ swilgt.sh -t "catch(time::now(_, _, _), _, halt(1))"

dram commented 7 years ago

SWI-Prolog will prompt for action for this case, instead of an infinite loop, i.e.:

% swipl -q -t foo
ERROR: '$runtoplevel'/0: Undefined procedure: foo/0
   Exception: (3) foo ?

Anyway, according to the document, I think I can use -g goal -t halt instead, which is what I needed.

Thanks!

pmoura commented 7 years ago

SWI-Prolog, by default, sets its debug_on_error flag to error and is apparently honoring this flag value for undefined procedures. But ::/2 is defined and thus not trapped.

dram commented 7 years ago

Thanks for your detailed explanation.

SWI-Prolog's behaviour seems a bit strange. I can reproduce this problem without Logtalk, e.g. swipl -t 'halt(foo)'. I will file an issue on SWI-Prolog's repo.

BTW, a small correction, value of debug_on_error is of type bool, according to the document.

JanWielemaker commented 7 years ago

There is no such thing as a different behaviour on different error exceptions. The system does have dedicated interactive behaviour for exceptions of the form error(Formal, Context) and exceptions that are not caught. AFAIK, the latter is what cause Logtalk programs to behave different from native programs with an error because Logtalk wraps toplevel goals in catch/3 and thus all errors are always caught as far as Prolog is concerned. Really uncaught exceptions bubble up to the C code that (re)starts the toplevel goal.

pmoura commented 7 years ago

@JanWielemaker Logtalk does wrap top-level ::/2 calls with a catch/3 but the error handler eventually throws the exception after possibly rewriting it. I.e. the errors are passed back to Prolog. Being uncaught exceptions, as you explain in your last sentence, the top-level goal is restarted thus resulting in the observed loop. Also note that:

?- catch(foo::bar, Error, true).
Error = error(existence_error(object, foo), logtalk(foo::bar, user)).

This suggests that we don't get the interactive behavior in this case for some other reason. Could it be due to the second argument of the error/2 exception term not being what SWI-Prolog expects?

JanWielemaker commented 7 years ago

The interactive behaviour of trapping the debugger at the location of the error happens because

  1. it is an error(_,_) exception
  2. debug_on_error flag is true
  3. It is an uncaught exception.

Your intermediate catch-rewrite-and-rethrow breaks the notion of being uncaught. Instead of rewriting the way you do, you might get away rewriting the exception as it happens using user:prolog_exception_hook/4. See library(prolog_stack) for an example.

pmoura commented 7 years ago

Thanks for explaining. The code that rewrites the exceptions is portable and thus unlikely to change (specially in this case where the advised use of the command-line options solves it: https://github.com/SWI-Prolog/swipl-devel/issues/203). I do have (commented out) code (in the adapters/swihooks.pl file) that uses user:prolog_exception_hook/4 for writing stack traces. But uncommenting this code doesn't seem to avoid the infinite loop in this case.

JanWielemaker commented 7 years ago

The infinite loop is by design. It is not a bug, but simply how -t is defined and I can't see anything wrong with that. Use -g if you don't want to restart on errors or wrap the goal in a catch/3. The exception hook serves a different purpose: get the debugger active in the error context and/or printing a nice stack trace. Both speedup development as they often avoid the need to restart the program under the debugger and carefully navigate to the error.