I'm bundling up a program for distribution to customers. I don't want it to ever enter the debugger. I want it to raise Java exceptions instead.
What I've written to achieve this follows. I've observed it working. However, there are still times when my program enters the debugger. I can't tell why, and want to prevent it entirely. Does anybody know? Does anyone on this list distribute ABCL applications to end users, and how do you disable the debugger?
/* disable the debugger. raise a RuntimeException instead */ static void installDebuggerHook() { Symbol.DEBUGGER_HOOK .setSymbolValue(new Function() { public LispObject execute(LispObject c, LispObject h) { throw translateCondition(c);}}); } static RuntimeException translateCondition(LispObject c) { return new RuntimeException( c instanceof Condition ? String.format("%s: %s", c.princToString(), ((Condition) c).getConditionReport()) : c.princToString()); }
My program is really a Lisp library that people will use via a thin Java layer. I have a hunch that the above takes care of errors that occur in: interpreter.eval("...") but not errors that occur in: someSymbol.execute(...)
Thanks in advance,
Vibhu
"place 2" below enters the debugger instead of raising the Java error specified by "place 0". Does anyone know if that's a bug in ABCL, or if I'm doing something wrong?
import org.armedbear.lisp.*; public class TestDebugger { public static void main(String[] args) { Interpreter interp = Interpreter.createInstance(); Symbol.DEBUGGER_HOOK.setSymbolValue( Packages.findPackage("SYSTEM") .findSymbol("%DEBUGGER-HOOK-FUNCTION") //place 0 .getSymbolFunction()); String exp = "(f 1)"; //a non-existent function try { interp.eval(exp); //place 1 } catch (Throwable e) {} interp.eval(exp); //place 2 p("end"); } static void p(Object o) {System.out.println(o);} }
Observations: 1. "place 1" does not enter the debugger, but correctly raises an exception. Why? It's no different from "place 2". 2. If variable "exp" is "(length 1)", then "place2" correctly causes the program to terminate immediately due to an exception. So it's just undefined functions that appear to be problematic, in that they produce the debugger.
Some (unsatisfactory) theories I have:
0. At "place 1", debug.lisp #'run-hook has re-bound *debugger-hook* to NIL. My hook raising an exception causes #'run-hook's progv to not complete. So the *debugger-hook* is left as NIL, causing the debugger to be entered at "place 2". 1. restart.lisp #'undefined-function-called calls "loop", which is a problem. 2. Or maybe it's that it establishes restarts, which is a problem. By comparison, in the "Observation 2" experiment above, there are no active restarts when my *debugger-hook* is called. 3. If I don't call eval directly, but do it through a wrapper such as this: static LispObject eval(String e) { LispThread th = LispThread.currentThread(); SpecialBindingsMark mark = th.markSpecialBindings(); try {return interpreter.eval(e);} finally {th.resetSpecialBindings(mark);} } then "place 2" above will correctly raise an exception instead of entering the debugger. But I don't understand this very well at all, so don't plan to do this.
Also, I'd said before that I suspected that interpreter.eval(...) was handled differently from someSymbol.execute(...) but my tests have shown that not to be the case at all.
It's specifically the second time onwards that you call an undefined function that lands you in the debugger.
Thanks for reading, and I'd love to hear if any of you have dealt with this before.
Vibhu
I want to describe progress I've made with this problem. I'll try and make this email stand alone.
I've attached a 41 line Java program that calls into ABCL. The unusual thing it does is to first install a *debugger-hook* that throws a Java RuntimeException, not a Condition. (This voids my warranty. What follows cannot therefore be called an ABCL bug.)
The output of the program is 1, unless I compile one of the Lisp functions it defines (line 14), in which case it is 2.
Here is why. There are two definitions of progv. The first is in SpecialOperators.java. That is used for interpreted Lisp code. The other is generated by compiler-pass2.lisp, and is used for compiled Lisp code. The former has a finally clause that does:
LispThread.currentThread().resetSpecialBindings(mark)
My hypothesis is that the latter does not do the equivalent in a finally clause.
---- The situation for LET is similar to that for PROGV (as line 11 notes).
==== I had said before that using a debugger hook like this would take care of the first error in the process's life, but not subsequent ones, which would all land me in the debugger. Now we know the reason. debug.lisp's #'run-hook has a PROGV. Because debug.lisp is compiled, the "bad" PROGV gets used and *DEBUGGER-HOOK* remains rebound to NIL forever.
I've worked around the problem for my project. One workaround is to load (but not compile) #'run-hook and #'invoke-debugger again. But that's not enough. signal.lisp's #'error rebinds *current-error-depth* in a LET, and that doesn't get unbound. So after a few errors we cross the *maximum-error-depth*. To solve that, we must also load (but not compile) signal.lisp's #'error. That works.
There's a simpler equivalent solution, which is to just define an interpreted #'error function that delegates to the original one like this:
eval("(let ((error-orig #'error))" + " (handler-bind ((style-warning #'(lambda (c)" + " (declare (ignore c))" + " (muffle-warning))))" + " (defun error (&rest a)" + " (let ()" + " (apply error-orig a)))))");
The LET (interpreted, so the "good" version in SpecialOperators.java is used) takes care of resetting special bindings even though error-orig throws a RuntimeException.
This is still not ideal. If you have the following calls:
Java code -> Lisp code (function1) -> Lisp code (function2)
where function2 calls #'error, then any special variables that were rebound by function1 are _not_ reset to their original values. For me, for now, that's tolerable. I suppose there's also then a memory leak in the special bindings data structure. I'll need to watch out for this as well.
---- Perhaps most people use ABCL more to use Java from Lisp instead of vice-versa as I'm doing. If ABCL is not your top level and you disable the debugger in production, I'd love to hear from you about how you did it.
Vibhu
armedbear-devel@common-lisp.net