disabling debugger in production

Vibhu Mohindra vibhu.mohindra at gmail.com
Wed Apr 26 19:02:50 UTC 2017


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
-------------- next part --------------
import org.armedbear.lisp.*;

public class TestDebugger4 {
  static Interpreter interp = Interpreter.createInstance();
  
  public static void main(String[] args) {
    installDebuggerHook();
    interp.eval("(defparameter *x* 1)");
    interp.eval("(defun f ()"
              + "  (progv '(*x*) '(2)"
//              + "  (let ((*x* 2))" //also has same problem as progv
              + "    (error \"test\")))");
    //with this line enabled, the program prints the incorrect value 2 not 1.
//    interp.eval("(compile 'f)");
    //the PROGV used for interpreted lisp is in SpecialOperators.java
    //but that code is not used for compiled lisp. Maybe the code generated
    //by compiler-pass2.lisp is used instead.
    //The former has a finally clause that is important.
    //My hypothesis is that the latter does not have the equivalent code in a
    //finally clause
    try {
      interp.eval("(f)");
    } catch (Exception expected) {}
    p(interp.eval("*x*").javaInstance()); //correct: 1, incorrect: 2
  }
  
  //copied from LispModule
  /* 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);}});
  }

  private static RuntimeException translateCondition(LispObject c) {
    return new RuntimeException(c.princToString());
  }

  static void p(Object o) {System.out.println(o);}
}


More information about the armedbear-devel mailing list