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
next prev parent 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).