all messages for Emacs-related lists mirrored at yhetil.org
 help / color / mirror / code / Atom feed
From: Stefan Monnier <monnier@iro.umontreal.ca>
To: Juanma Barranquero <lekktu@gmail.com>
Cc: Katsumi Yamaoka <yamaoka@jpl.org>,
	ivan.kanis@googlemail.com, asjo@koldfront.dk,
	emacs-devel@gnu.org
Subject: Re: Simple defadvice's stopped working (commit daa84a03, Thu Nov 8 23:10:16 2012 -0500)
Date: Fri, 16 Nov 2012 15:11:46 -0500	[thread overview]
Message-ID: <jwvfw49qpg3.fsf-monnier+emacs@gnu.org> (raw)
In-Reply-To: <CAAeL0SRabTPq3qW6Zad2r+0KiGp=D5Cmc9=CDrTJ18zDsfq7sw@mail.gmail.com> (Juanma Barranquero's message of "Fri, 16 Nov 2012 19:17:39 +0100")

>> My code can handle your example just fine.
>> The problem comes when an around advice is added after your advice, at
>> which point your advice won't work any more (not because your is an
>> around advice, but because it's "hidden" by another around advice).
> Which around advice is being added after my advice?

I don't know that there is any, but there could be.  In any case, you're
not using my code yet.  If you want to try it out, see below.
I think I got it to handle around advice now as well.


        Stefan


(eval-when-compile                      ;Poor man's macrolet.
  (defmacro interactive--get-next-frame ()
    `(progn
       (setq frame nextframe)
       (setq nextframe (backtrace-frame (setq i (1+ i))))
       ;; (message "Frame %d = %S" i nextframe)
       )))
(defun called-interactively-p (&optional kind)
  "Return t if the containing function was called by `call-interactively'.
If KIND is `interactive', then only return t if the call was made
interactively by the user, i.e. not in `noninteractive' mode nor
when `executing-kbd-macro'.
If KIND is `any', on the other hand, it will return t for any kind of
interactive call, including being called as the binding of a key or
from a keyboard macro, even in `noninteractive' mode.

This function is very brittle, it may fail to return the intended result when 
the code is debugged, advised, or instrumented in some form.  Some macros and
special forms (such as `condition-case') may also sometimes wrap their bodies
in a `lambda', so any call to `called-interactively-p' from those bodies will 
indicate whether that lambda (rather than the surrounding function) was called
interactively.

Instead of using this function, it is cleaner and more reliable to give your
function an extra optional argument whose `interactive' spec specifies
non-nil unconditionally (\"p\" is a good way to do this), or via
\(not (or executing-kbd-macro noninteractive)).

The only known proper use of `interactive' for KIND is in deciding
whether to display a helpful message, or how to display it.  If you're
thinking of using it for any other purpose, it is quite likely that
you're making a mistake.  Think: what do you want to do when the
command is called from a keyboard macro?"
  (declare (advertised-calling-convention (kind) "23.1"))
  (when (not (and (eq kind 'interactive)
                  (or executing-kbd-macro noninteractive)))
    (let ((i 0)
          frame nextframe)
      (interactive--get-next-frame) ;; Get the first frame.
      (while
          ;; FIXME: The edebug and advice handling should be made modular and
          ;; provided directly by edebug.el and nadvice.el.
          (progn
            (interactive--get-next-frame)
            ;; `pcase' would be a fairly good fit here, but it sometimes moves
            ;; branches within local functions, which then messes up the
            ;; `backtrace-frame' data we get,
            (or
             ;; First comes the binding for to sm-called-interactively-p and
             ;; maybe also for interactive-p.
             (memq (nth 1 frame) '(interactive-p called-interactively-p))
             ;; Then may come special forms (for non-compiled code).
             (null (car frame))
             ;; In byte-compiled code, subexpressions of things like
             ;; condition-case are wrapped in a separate bytecode chunk.
             ;; FIXME: For lexical-binding code, this is much worse, because
             ;; the frames look like "byte-code -> funcall -> #[...]", which
             ;; is not a reliable signature.
             (eq (nth 1 frame) 'byte-code)
             ;; When edebugging a function, some of the sub-expressions are
             ;; wrapped in (lambda () ..).
             (when (and (eq (car-safe (nth 1 frame)) 'lambda)
                        (eq (nth 1 (nth 1 frame)) '())
                        (eq (nth 1 nextframe) 'edebug-enter))
               (interactive--get-next-frame)
               ;; `edebug-enter' calls itself on its first invocation.
               (when (eq (nth 1 nextframe) 'edebug-enter)
                 (interactive--get-next-frame))
               t)
             ;; When the code is advised, we also have a problem.
             ;; FIXME: The Major Ugly Hack below will not handle calls to
             ;; called-interactively-p done from the advised function if the
             ;; deepest advice is an around advice!
             ;; In other cases (calls from an advice or calls from the advised
             ;; function when the deepest advice is not an around advice), it
             ;; should hopefully get it right.
             (when (and (eq (nth 1 nextframe) 'apply) (fboundp 'advice--p)
                        (advice--p (indirect-function
                                    (nth 1 (backtrace-frame (1+ i))))))
               (interactive--get-next-frame)
               (interactive--get-next-frame)
               ;; If we now have the symbol, this was the head advice and
               ;; we're done.
               (while (advice--p (nth 1 frame))
                 ;; This was an inner advice called from some earlier advice.
                 ;; The stack frames look different depending on the particular
                 ;; kind of the earlier advice.
                 (if (and (eq (nth 1 nextframe) 'apply)
                          (advice--p (indirect-function
                                      (nth 1 (backtrace-frame (1+ i))))))
                     ;; The earlier advice was something like a before/after
                     ;; advice where the "next" code is called directly by the
                     ;; advice--p object.
                     (progn
                       (interactive--get-next-frame)
                       (interactive--get-next-frame))
                   ;; It's apparently an around advice, where the "next" is
                   ;; called by the body of the advice in any way it sees fit,
                   ;; so we need to skip the frames of that body.
                   (let ((inneradvice (nth 1 frame))
                         (j i) framej)
                     (while
                         (progn
                           (setq framej (backtrace-frame (setq j (1+ j))))
                           (not (and (eq (nth 1 framej) 'apply)
                                     (eq (nth 3 framej) inneradvice)))))
                     (setq i j)
                     (interactive--get-next-frame)
                     (interactive--get-next-frame)))))
             )))
      ;; Now `frame' should be "the function from which we were called".
      (pcase (cons frame nextframe)
        ;; No subr calls `interactive-p', so we can rule that out.
        (`((,_ ,(pred (lambda (f) (subrp (indirect-function f)))) . ,_) . ,_) nil)
        ;; Somehow, I sometimes got `command-execute' rather than
        ;; `call-interactively' on my stacktrace !?
        ;;(`(,_ . (t command-execute . ,_)) t)
        (`(,_ . (t call-interactively . ,_)) t)))))



  reply	other threads:[~2012-11-16 20:11 UTC|newest]

Thread overview: 24+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2012-11-09 23:47 Simple defadvice's stopped working (commit daa84a03, Thu Nov 8 23:10:16 2012 -0500) Adam Sjøgren
2012-11-10  1:55 ` Stefan Monnier
2012-11-11 21:24   ` Tim Cross
2012-11-12 10:36 ` Ivan Kanis
2012-11-13 11:09   ` Katsumi Yamaoka
2012-11-13 14:34     ` Stefan Monnier
2012-11-13 23:18       ` Katsumi Yamaoka
2012-11-14 16:19         ` Juanma Barranquero
2012-11-15  2:08           ` Katsumi Yamaoka
2012-11-15  3:30             ` Stefan Monnier
2012-11-15  9:00               ` Ivan Kanis
2012-11-15 10:55                 ` Katsumi Yamaoka
2012-11-15 14:39                   ` Stefan Monnier
2012-11-15 23:01                     ` Katsumi Yamaoka
2012-11-16 14:16                       ` Stefan Monnier
2012-11-16 16:33                         ` Juanma Barranquero
2012-11-16 17:25                           ` Stefan Monnier
2012-11-16 17:36                             ` Juanma Barranquero
2012-11-16 18:11                             ` Stefan Monnier
2012-11-16 18:17                               ` Juanma Barranquero
2012-11-16 20:11                                 ` Stefan Monnier [this message]
2012-11-16 23:52                                   ` Juanma Barranquero
2012-11-19  1:29                                     ` Stefan Monnier
2012-11-19  2:36                                       ` Juanma Barranquero

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=jwvfw49qpg3.fsf-monnier+emacs@gnu.org \
    --to=monnier@iro.umontreal.ca \
    --cc=asjo@koldfront.dk \
    --cc=emacs-devel@gnu.org \
    --cc=ivan.kanis@googlemail.com \
    --cc=lekktu@gmail.com \
    --cc=yamaoka@jpl.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
Code repositories for project(s) associated with this external index

	https://git.savannah.gnu.org/cgit/emacs.git
	https://git.savannah.gnu.org/cgit/emacs/org-mode.git

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.