Skip to content

Commit 2dfeea8

Browse files
committed
Fix reader infinite recursion for circular mixed-type values
Make sure that the value added to the `read_objects_completed` set is the one we actually return; previously this wasn't the case for conses because of an optimisation (bug#54501). Also add a check for vacuous self-references such as #1=#1# instead of returning a nonsense value from thin air. * src/lread.c (read1): Treat numbered conses correctly as described above. Detect vacuous self-references. * test/src/lread-tests.el (lread-test-read-and-print) (lread-test-circle-cases, lread-circle): Add tests.
1 parent e96061d commit 2dfeea8

File tree

2 files changed

+52
-16
lines changed

2 files changed

+52
-16
lines changed

src/lread.c

Lines changed: 30 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -3488,6 +3488,29 @@ read1 (Lisp_Object readcharfun, int *pch, bool first_in_list, bool locate_syms)
34883488
/* Read the object itself. */
34893489
Lisp_Object tem = read0 (readcharfun, locate_syms);
34903490

3491+
if (CONSP (tem))
3492+
{
3493+
if (BASE_EQ (tem, placeholder))
3494+
/* Catch silly games like #1=#1# */
3495+
invalid_syntax ("nonsensical self-reference",
3496+
readcharfun);
3497+
3498+
/* Optimisation: since the placeholder is already
3499+
a cons, repurpose it as the actual value.
3500+
This allows us to skip the substition below,
3501+
since the placeholder is already referenced
3502+
inside TEM at the appropriate places. */
3503+
Fsetcar (placeholder, XCAR (tem));
3504+
Fsetcdr (placeholder, XCDR (tem));
3505+
3506+
struct Lisp_Hash_Table *h2
3507+
= XHASH_TABLE (read_objects_completed);
3508+
ptrdiff_t i = hash_lookup (h2, placeholder, &hash);
3509+
eassert (i < 0);
3510+
hash_put (h2, placeholder, Qnil, hash);
3511+
return placeholder;
3512+
}
3513+
34913514
/* If it can be recursive, remember it for
34923515
future substitutions. */
34933516
if (! SYMBOLP (tem)
@@ -3502,24 +3525,15 @@ read1 (Lisp_Object readcharfun, int *pch, bool first_in_list, bool locate_syms)
35023525
}
35033526

35043527
/* Now put it everywhere the placeholder was... */
3505-
if (CONSP (tem))
3506-
{
3507-
Fsetcar (placeholder, XCAR (tem));
3508-
Fsetcdr (placeholder, XCDR (tem));
3509-
return placeholder;
3510-
}
3511-
else
3512-
{
3513-
Flread__substitute_object_in_subtree
3514-
(tem, placeholder, read_objects_completed);
3528+
Flread__substitute_object_in_subtree
3529+
(tem, placeholder, read_objects_completed);
35153530

3516-
/* ...and #n# will use the real value from now on. */
3517-
i = hash_lookup (h, number, &hash);
3518-
eassert (i >= 0);
3519-
set_hash_value_slot (h, i, tem);
3531+
/* ...and #n# will use the real value from now on. */
3532+
i = hash_lookup (h, number, &hash);
3533+
eassert (i >= 0);
3534+
set_hash_value_slot (h, i, tem);
35203535

3521-
return tem;
3522-
}
3536+
return tem;
35233537
}
35243538

35253539
/* #n# returns a previously read object. */

test/src/lread-tests.el

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -258,5 +258,27 @@ literals (Bug#20852)."
258258
(should (equal (read "-0.e-5") -0.0))
259259
)
260260

261+
(defun lread-test-read-and-print (str)
262+
(let* ((read-circle t)
263+
(print-circle t)
264+
(val (read-from-string str)))
265+
(if (consp val)
266+
(prin1-to-string (car val))
267+
(error "reading %S failed: %S" str val))))
268+
269+
(defconst lread-test-circle-cases
270+
'("#1=(#1# . #1#)"
271+
"#1=[#1# a #1#]"
272+
"#1=(#2=[#1# #2#] . #1#)"
273+
"#1=(#2=[#1# #2#] . #2#)"
274+
"#1=[#2=(#1# . #2#)]"
275+
"#1=(#2=[#3=(#1# . #2#) #4=(#3# . #4#)])"
276+
))
277+
278+
(ert-deftest lread-circle ()
279+
(dolist (str lread-test-circle-cases)
280+
(ert-info (str :prefix "input: ")
281+
(should (equal (lread-test-read-and-print str) str))))
282+
(should-error (read-from-string "#1=#1#") :type 'invalid-read-syntax))
261283

262284
;;; lread-tests.el ends here

0 commit comments

Comments
 (0)