unofficial mirror of bug-gnu-emacs@gnu.org 
 help / color / mirror / code / Atom feed
From: "Mattias Engdegård" <mattiase@acm.org>
To: Paul Pogonyshev <pogonyshev@gmail.com>
Cc: Michael Heerdegen <michael_heerdegen@web.de>,
	Stefan Monnier <monnier@iro.umontreal.ca>,
	51982@debbugs.gnu.org
Subject: bug#51982: Erroneous handling of local variables in byte-compiled nested lambdas
Date: Sat, 20 Nov 2021 19:34:03 +0100	[thread overview]
Message-ID: <DCFA122D-7C44-4DE3-9523-BF5D7D877D13@acm.org> (raw)
In-Reply-To: <CAG7BparLwp_cP51hhvOME_DaoDDdc8yuyLiUz2bbRimoNUzXZQ@mail.gmail.com>

[-- Attachment #1: Type: text/plain, Size: 585 bytes --]

20 nov. 2021 kl. 18.22 skrev Paul Pogonyshev <pogonyshev@gmail.com>:

> > you cannot count on an eventual fix to be back-ported to Emacs 28
> 
> Uh, it's not even released yet. Are the stabilization rules so strict that even a *fix* cannot go in? Unless it is going to be a rewrite of half the file...

Sorry, I'd love to have it in 28 but I don't make the rules.

Meanwhile, here's a lightly tested attempt at a fix. I'm not very satisfied by it because it forces an up-front access of a captured variable; I'd rather sink it to each application of the λ-lifted function.


[-- Attachment #2: cconv-bug51982.diff --]
[-- Type: application/octet-stream, Size: 5886 bytes --]

diff --git a/lisp/emacs-lisp/cconv.el b/lisp/emacs-lisp/cconv.el
index 03e109f250..17deb8b90a 100644
--- a/lisp/emacs-lisp/cconv.el
+++ b/lisp/emacs-lisp/cconv.el
@@ -431,7 +431,8 @@ cconv-convert
                  (let ((closedsym (make-symbol (format "closed-%s" var))))
                    (setq new-env (cconv--remap-llv new-env var closedsym))
                    (setq new-extend (cons closedsym (remq var new-extend)))
-                   (push `(,closedsym ,var) binders-new)))
+                   (let ((var-def (or (cdr (assq var env)) var)))
+                     (push `(,closedsym ,var-def) binders-new))))
 
                ;; We push the element after redefined free variables are
                ;; processed.  This is important to avoid the bug when free
@@ -456,7 +457,8 @@ cconv-convert
                     (closedsym (make-symbol (format "closed-%s" var))))
                (setq new-env (cconv--remap-llv new-env var closedsym))
                (setq new-extend (cons closedsym (remq var new-extend)))
-               (push `(,closedsym ,var) binders-new)))))
+               (let ((var-def (or (cdr (assq var env)) var)))
+                 (push `(,closedsym ,var-def) binders-new))))))
 
        `(,letsym ,(nreverse binders-new)
                  . ,(mapcar (lambda (form)
diff --git a/test/lisp/emacs-lisp/bytecomp-tests.el b/test/lisp/emacs-lisp/bytecomp-tests.el
index dbc0aa3db4..ed596ef3b4 100644
--- a/test/lisp/emacs-lisp/bytecomp-tests.el
+++ b/test/lisp/emacs-lisp/bytecomp-tests.el
@@ -643,6 +643,15 @@ bytecomp-tests--test-cases
 
     (cond)
     (mapcar (lambda (x) (cond ((= x 0)))) '(0 1))
+
+    ;; This gives different results in lexbind and dynbind modes,
+    ;; but in each the compiler and interpreter should agree!
+    (let ((f (lambda (x)
+               (lambda ()
+                 (let ((g (lambda () x)))
+                   (let ((x 'a))
+                     (list x (funcall g))))))))
+      (funcall (funcall f 'b)))
     )
   "List of expressions for cross-testing interpreted and compiled code.")
 
diff --git a/test/lisp/emacs-lisp/cconv-tests.el b/test/lisp/emacs-lisp/cconv-tests.el
index 4290571735..b6a789fcfe 100644
--- a/test/lisp/emacs-lisp/cconv-tests.el
+++ b/test/lisp/emacs-lisp/cconv-tests.el
@@ -205,5 +205,88 @@ cconv-convert-lambda-lifted
            nil 99)
           42)))
 
+(defun cconv-tests--intern-all (x)
+  "Intern all symbols in X."
+  (cond ((symbolp x) (intern (symbol-name x)))
+        ((consp x) (cons (cconv-tests--intern-all (car x))
+                         (cconv-tests--intern-all (cdr x))))
+        ;; Assume we don't need to deal with vectors etc.
+        (t x)))
+
+(ert-deftest cconv-closure-convert-remap-var ()
+  ;; Verify that we correctly remap shadowed lambda-lifted variables.
+
+  ;; We intern all symbols for ease of comparison; this works because
+  ;; the `cconv-closure-convert' result should contain no pair of
+  ;; distinct symbols having the same name.
+
+  ;; Sanity check: captured variable, no lambda-lifting or shadowing:
+  (should (equal (cconv-tests--intern-all
+           (cconv-closure-convert
+            '#'(lambda (x)
+                 #'(lambda () x))))
+           '#'(lambda (x)
+                (internal-make-closure
+                 nil (x) nil
+                 (internal-get-closed-var 0)))))
+
+  ;; Basic case:
+  ;; - with `let':
+  (should (equal (cconv-tests--intern-all
+                  (cconv-closure-convert
+                   '#'(lambda (x)
+                        (let ((f #'(lambda () x)))
+                          (let ((x 'b))
+                            (list x (funcall f)))))))
+                 '#'(lambda (x)
+                      (let ((f #'(lambda (x) x)))
+                        (let ((x 'b)
+                              (closed-x x))
+                          (list x (funcall f closed-x)))))))
+  ;; - with `let*':
+  (should (equal (cconv-tests--intern-all
+                  (cconv-closure-convert
+                   '#'(lambda (x)
+                        (let ((f #'(lambda () x)))
+                          (let* ((x 'b))
+                            (list x (funcall f)))))))
+                 '#'(lambda (x)
+                      (let ((f #'(lambda (x) x)))
+                        (let* ((closed-x x)
+                               (x 'b))
+                          (list x (funcall f closed-x)))))))
+
+  ;; With the lambda-lifted shadowed variable also being captured:
+  ;; - with `let':
+  (should (equal (cconv-tests--intern-all
+                  (cconv-closure-convert
+                   '#'(lambda (x)
+                        #'(lambda ()
+                            (let ((f #'(lambda () x)))
+                              (let ((x 'a))
+                                (list x (funcall f))))))))
+                 '#'(lambda (x)
+                      (internal-make-closure
+                       nil (x) nil
+                       (let ((f #'(lambda (x) x)))
+                         (let ((x 'a)
+                               (closed-x (internal-get-closed-var 0)))
+                           (list x (funcall f closed-x))))))))
+  ;; - with `let*':
+  (should (equal (cconv-tests--intern-all
+                  (cconv-closure-convert
+                   '#'(lambda (x)
+                        #'(lambda ()
+                            (let ((f #'(lambda () x)))
+                              (let* ((x 'a))
+                                (list x (funcall f))))))))
+                 '#'(lambda (x)
+                      (internal-make-closure
+                       nil (x) nil
+                       (let ((f #'(lambda (x) x)))
+                         (let* ((closed-x (internal-get-closed-var 0))
+                                (x 'a))
+                           (list x (funcall f closed-x)))))))))
+
 (provide 'cconv-tests)
 ;;; cconv-tests.el ends here

  reply	other threads:[~2021-11-20 18:34 UTC|newest]

Thread overview: 22+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2021-11-19 20:31 bug#51982: Erroneous handling of local variables in byte-compiled nested lambdas Paul Pogonyshev
2021-11-20  4:44 ` Michael Heerdegen
2021-11-20  8:45   ` Mattias Engdegård
2021-11-20 10:51     ` Michael Heerdegen
2021-11-20 16:54   ` Paul Pogonyshev
2021-11-20 17:04     ` Mattias Engdegård
2021-11-20 17:22       ` Paul Pogonyshev
2021-11-20 18:34         ` Mattias Engdegård [this message]
2021-11-20 20:53           ` Paul Pogonyshev
2021-11-21  7:59         ` Michael Heerdegen
2021-11-21  9:59           ` Mattias Engdegård
2021-11-22 10:29             ` Michael Heerdegen
2021-11-22 13:56               ` Mattias Engdegård
2021-11-22 17:35                 ` Mattias Engdegård
2021-11-30 14:12                   ` Stefan Monnier via Bug reports for GNU Emacs, the Swiss army knife of text editors
2021-11-30 17:01                     ` Mattias Engdegård
2021-11-30 22:41                       ` Stefan Monnier via Bug reports for GNU Emacs, the Swiss army knife of text editors
2021-12-01 16:04                         ` Mattias Engdegård
2021-12-01 18:34                           ` Stefan Monnier via Bug reports for GNU Emacs, the Swiss army knife of text editors
2021-12-01 22:32                             ` Mattias Engdegård
2021-12-02  9:13                               ` Mattias Engdegård
2022-09-09 17:59                                 ` Lars Ingebrigtsen

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

  List information: https://www.gnu.org/software/emacs/

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

  git send-email \
    --in-reply-to=DCFA122D-7C44-4DE3-9523-BF5D7D877D13@acm.org \
    --to=mattiase@acm.org \
    --cc=51982@debbugs.gnu.org \
    --cc=michael_heerdegen@web.de \
    --cc=monnier@iro.umontreal.ca \
    --cc=pogonyshev@gmail.com \
    /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 public inbox

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

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).