commit 72b50901ef95410810b4ca7ecf103f028b8ae4b4 Author: Stefan Monnier Date: Mon May 25 17:01:07 2026 -0400 lisp/emacs-lisp/package.el (package-quickstart-refresh): Delete stale elc diff --git a/lisp/emacs-lisp/package.el b/lisp/emacs-lisp/package.el index 068b94360d4..9ff761f0157 100644 --- a/lisp/emacs-lisp/package.el +++ b/lisp/emacs-lisp/package.el @@ -4814,6 +4814,10 @@ find FILE." ;; byte-compile-warnings: (not make-local) ;; End: ")) + (with-demoted-errors "%S" + ;; The `.elc' file is now stale. Remove it so it doesn't affect + ;; its own compilation or lingers in case of compilation failure. + (delete-file (concat package-quickstart-file "c"))) ;; FIXME: Do it asynchronously in an Emacs subprocess, and ;; don't show the byte-compiler warnings. (byte-compile-file package-quickstart-file))) commit ea2110b6e56ea4dc8d9fbe3ee19a7f56ffafa87f Author: Eli Zaretskii Date: Mon May 25 18:39:26 2026 +0300 ; * src/sfntfont-android.c (GET_SCANLINE_BUFFER): Fix a typo. diff --git a/src/sfntfont-android.c b/src/sfntfont-android.c index adf9bf74632..55f9c9e9078 100644 --- a/src/sfntfont-android.c +++ b/src/sfntfont-android.c @@ -95,7 +95,7 @@ static size_t max_scanline_buffer_size; } \ else if (_size <= scanline_buffer.buffer_size) \ (buffer) = scanline_buffer.buffer_data; \ - /* This is unreachable but clang says it is isn't. */\ + /* This is unreachable but clang says it isn't. */ \ else \ emacs_abort (); \ \ commit 217064e9dca2b9d4b55e0fd823017b4ee07163e9 Author: Harald Jörg Date: Mon May 25 11:23:34 2026 +0200 ;cperl-mode.el: Fix fontification edge cases These were reported by happy-barney on GitHub https://github.com/HaraldJoerg/cperl-mode/issues * lisp/progmodes/cperl-mode.el (cperl-init-faces): Don't mistake $method as a method declaration. Move matcher for "use require" higher to prevent "require" being fontified as keyword. * test/lisp/progmodes/cperl-mode-resources/sub-names.pl: Add a test case for $method * test/lisp/progmodes/cperl-mode-tests.el (cperl-test-fontify-declarations): Add a test case for a module name looking like a keyword (cperl-test-fontify-sub-names): Verify that $method does not declare a method diff --git a/lisp/progmodes/cperl-mode.el b/lisp/progmodes/cperl-mode.el index d3014fee2b7..91e2e46fdba 100644 --- a/lisp/progmodes/cperl-mode.el +++ b/lisp/progmodes/cperl-mode.el @@ -6353,7 +6353,7 @@ functions (which they are not). Inherits from `default'.") ;; facespec is evaluated depending on whether the ;; statement ends in a "{" (definition) or ";" ;; (declaration without body) - (list (concat "\\<" cperl-sub-regexp + (list (concat "\\(?:\\`\\|[^$%@*&]\\)" cperl-sub-regexp ;; group 1: optional subroutine name (rx (sequence (eval cperl--ws+-rx) @@ -6400,7 +6400,24 @@ functions (which they are not). Inherits from `default'.") (error (match-end 2)))) nil (1 font-lock-variable-name-face))) - ;; -------- flow control + ;; -------- various stuff calling for a package name + ;; (matcher (subexp facespec) (subexp facespec)) + `(,(rx (sequence + (or (sequence (or line-start space "{" ) + (group-n 1 (or "package" "require" "use" + "import" "no" "bootstrap" "class")) + (eval cperl--ws+-rx)) + (sequence (group-n 2 (sequence ":" + (eval cperl--ws*-rx) + "isa")) + "(" + (eval cperl--ws*-rx))) + (group-n 3 (eval cperl--normal-identifier-rx)) + (any " \t\n;)"))) ; require A if B; + (1 font-lock-keyword-face t t) + (2 font-lock-constant-face t t) + (3 font-lock-function-name-face)) + ;; -------- flow control ;; (matcher . subexp) font-lock-keyword-face by default ;; This highlights declarations and definitions differently. ;; We do not try to highlight in the case of attributes: @@ -6507,22 +6524,6 @@ functions (which they are not). Inherits from `default'.") ;; (matcher subexp facespec) '("-[rwxoRWXOezsfdlpSbctugkTBMAC]\\>\\([ \t]+_\\>\\)?" 0 font-lock-function-name-face keep) ; Not very good, triggers at "[a-z]" - ;; -------- various stuff calling for a package name - ;; (matcher (subexp facespec) (subexp facespec)) - `(,(rx (sequence - (or (sequence (or line-start space "{" ) - (or "package" "require" "use" "import" - "no" "bootstrap" "class") - (eval cperl--ws+-rx)) - (sequence (group-n 2 (sequence ":" - (eval cperl--ws*-rx) - "isa")) - "(" - (eval cperl--ws*-rx))) - (group-n 1 (eval cperl--normal-identifier-rx)) - (any " \t\n;)"))) ; require A if B; - (1 font-lock-function-name-face) - (2 font-lock-constant-face t t)) ;; -------- formats ;; (matcher subexp facespec) '("^[ \t]*format[ \t]+\\([a-zA-Z_][a-zA-Z_0-9:]*\\)[ \t]*=[ \t]*$" diff --git a/test/lisp/progmodes/cperl-mode-resources/sub-names.pl b/test/lisp/progmodes/cperl-mode-resources/sub-names.pl index 46d05b4dbd2..229106865a3 100644 --- a/test/lisp/progmodes/cperl-mode-resources/sub-names.pl +++ b/test/lisp/progmodes/cperl-mode-resources/sub-names.pl @@ -17,6 +17,15 @@ # This comment has a method name in it, and we don't want "method" # to be fontified as a keyword, nor "name" fontified as a name. +# Next is a variable named "$method" followed by a keyword. This +# keyword is not a subroutine name and should not be fontified +# accordingly. Reported by Branislav Zahradnik, +# https://github.com/HaraldJoerg/cperl-mode/issues/24 + +push @abstract, $method + unless defined &$method + ; + __END__ =head1 Test using the keywords POD diff --git a/test/lisp/progmodes/cperl-mode-tests.el b/test/lisp/progmodes/cperl-mode-tests.el index 117eb9fdf9a..ffb79c6e5a2 100644 --- a/test/lisp/progmodes/cperl-mode-tests.el +++ b/test/lisp/progmodes/cperl-mode-tests.el @@ -143,7 +143,8 @@ point in the distant past, and is still broken in perl-mode. " (with-temp-buffer (funcall cperl-test-mode) (insert "package Foo::Bar;\n") - (insert "use Fee::Fie::Foe::Foo\n;") + (insert "use Fee::Fie::Foe::Foo\n;\n") + (insert "use require::relative;\n") ; module name has a keyword (insert "my $xyzzy = 'PLUGH';\n") (goto-char (point-min)) (font-lock-ensure) @@ -153,9 +154,15 @@ point in the distant past, and is still broken in perl-mode. " (search-forward "use") ; This was buggy in perl-mode (should (equal (get-text-property (match-beginning 0) 'face) 'font-lock-keyword-face)) - (search-forward "my") - (should (equal (get-text-property (match-beginning 0) 'face) - 'font-lock-keyword-face)))) + (re-search-forward (rx(sequence(group-n 1 "use") + (1+ blank) + (group-n 2 "require")))) + (should (equal (get-text-property (match-beginning 1) 'face) + 'font-lock-keyword-face)) + (should (equal (get-text-property (match-beginning 2) 'face) + (if (eq cperl-test-mode #'cperl-mode) + 'font-lock-function-name-face + 'font-lock-constant-face))))) (ert-deftest cperl-test-fontify-attrs-and-signatures () "Test fontification of the various combinations of subroutine @@ -330,13 +337,17 @@ comments and POD they should be fontified as POD." (should (equal (get-text-property (match-beginning 1) 'face) (if (equal cperl-test-mode 'perl-mode) nil 'cperl-method-call))) - ;; POD + ;; comment (search-forward-regexp "\\(method\\) \\(name\\)") (should (equal (get-text-property (match-beginning 1) 'face) 'font-lock-comment-face)) (should (equal (get-text-property (match-beginning 2) 'face) 'font-lock-comment-face)) - ;; comment + ;; false positive: $method is not a method + (search-forward-regexp "\\($method\\)\\(?:\n\\|\\s-\\)+\\(unless\\)") + (should (equal (get-text-property (match-beginning 2) 'face) + 'font-lock-keyword-face)) + ;; POD (search-forward-regexp "\\(method\\) \\(name\\)") (should (equal (get-text-property (match-beginning 1) 'face) 'font-lock-comment-face)) commit 6d15d68e1f77ebb81827d792fbc67363dd5b730c Author: Po Lu Date: Mon May 25 11:50:03 2026 +0800 ; * src/sfntfont-android.c (GET_SCANLINE_BUFFER): Correct commentary. diff --git a/src/sfntfont-android.c b/src/sfntfont-android.c index 30cf1876191..adf9bf74632 100644 --- a/src/sfntfont-android.c +++ b/src/sfntfont-android.c @@ -95,7 +95,7 @@ static size_t max_scanline_buffer_size; } \ else if (_size <= scanline_buffer.buffer_size) \ (buffer) = scanline_buffer.buffer_data; \ - /* This is unreachable but clang says it is. */ \ + /* This is unreachable but clang says it is isn't. */\ else \ emacs_abort (); \ \ @@ -127,7 +127,7 @@ static size_t max_scanline_buffer_size; } \ else if (_size <= scanline_buffer.buffer_size) \ (buffer) = scanline_buffer.buffer_data; \ - /* This is unreachable but clang says it is. */ \ + /* This is unreachable but clang says it isn't. */ \ else \ emacs_abort (); \ \ commit d6215451fad244c6947dc6f67c039969853e6b12 Author: Po Lu Date: Mon May 25 11:29:32 2026 +0800 Fix parsing of font metadata tables on Android * src/sfnt.c (sfnt_read_meta_table): Allocate `directory->length' bytes after the map rather than in place of it. diff --git a/src/sfnt.c b/src/sfnt.c index 4e4e2e121e6..956b89d3efb 100644 --- a/src/sfnt.c +++ b/src/sfnt.c @@ -5856,7 +5856,7 @@ sfnt_read_meta_table (int fd, struct sfnt_offset_subtable *subtable) if (ckd_mul (&map_size, sizeof *meta->data_maps, meta->num_data_maps) /* Do so while checking for overflow from bad sfnt files. */ || directory->length - required < map_size - || ckd_add (&data_size, data_size, directory->length)) + || ckd_add (&data_size, map_size, directory->length)) { xfree (meta); return NULL; commit 7cef36258148e2c202535b5761df8545718a27bf Author: Po Lu Date: Mon May 25 11:23:10 2026 +0800 Fix the Android build * src/sfnt.c (sfnt_fill_span) [NDEBUG]: Don't access `row_end' when not defined. diff --git a/src/sfnt.c b/src/sfnt.c index 4900daee6f6..4e4e2e121e6 100644 --- a/src/sfnt.c +++ b/src/sfnt.c @@ -4363,8 +4363,10 @@ sfnt_fill_span (struct sfnt_raster *raster, sfnt_fixed y, if ((left & ~SFNT_POLY_MASK) == (right & ~SFNT_POLY_MASK)) { +#ifndef NDEBUG /* Assert that start does not exceed the end of the row. */ eassert (start <= row_end); +#endif /* !NDEBUG */ w = coverage[right - left]; a = *start + w; @@ -4379,8 +4381,10 @@ sfnt_fill_span (struct sfnt_raster *raster, sfnt_fixed y, if (left & SFNT_POLY_MASK) { +#ifndef NDEBUG /* Assert that start does not exceed the end of the row. */ eassert (start <= row_end); +#endif /* !NDEBUG */ /* Compute the coverage for the first pixel, and move left past it. The coverage is a number from 1 to 7 describing how @@ -4406,8 +4410,10 @@ sfnt_fill_span (struct sfnt_raster *raster, sfnt_fixed y, /* Fill pixels between left and right. */ while (left + SFNT_POLY_MASK < right) { +#ifndef NDEBUG /* Assert that start does not exceed the end of the row. */ eassert (start <= row_end); +#endif /* !NDEBUG */ a = *start + w; *start++ = sfnt_saturate_short (a); @@ -4418,8 +4424,10 @@ sfnt_fill_span (struct sfnt_raster *raster, sfnt_fixed y, if (right & SFNT_POLY_MASK) { +#ifndef NDEBUG /* Assert that start does not exceed the end of the row. */ eassert (start <= row_end); +#endif /* !NDEBUG */ w = coverage[right - left]; a = *start + w; commit 44013f6be751363f7c4c642ebbfd618110db8b75 Author: Po Lu Date: Mon May 25 11:21:00 2026 +0800 Revert "Don’t silently truncate file names in exec.c" This reverts commit 3461b450c5eae3ed53192aa9514e0b1ac1b1c8f2. Gnulib and intprops.h are not available from within the exec helper. diff --git a/exec/exec.c b/exec/exec.c index ace62dd0191..7736c0dab27 100644 --- a/exec/exec.c +++ b/exec/exec.c @@ -863,25 +863,32 @@ insert_args (struct exec_tracee *tracee, USER_REGS_STRUCT *regs, -/* Format PID, a nonnegative process identifier, in base 10. - Place the result in *IN. Do not null-terminate the result. - Possibly modify the bytes in IN that are after the result. - Return a pointer to the byte after the result. */ +/* Format PID, an unsigned process identifier, in base 10. Place the + result in *IN, and return a pointer to the byte after the + result. REM should be NULL. */ char * -format_pid (char in[INT_STRLEN_BOUND (pid_t)], pid_t pid) +format_pid (char *in, unsigned int pid) { - char *pend = in + INT_STRLEN_BOUND (pid_t); - char *p = pend; + unsigned int digits[32], *fill; - do - *--p = '0' + pid % 10; - while ((pid /= 10) != 0); + fill = digits; - do - *in++ = *p++; - while (p < pend); + for (; pid != 0; pid = pid / 10) + *fill++ = pid % 10; + + /* Insert 0 if the number would otherwise be empty. */ + + if (fill == digits) + *fill++ = 0; + + while (fill != digits) + { + --fill; + *in++ = '0' + *fill; + } + *in = '\0'; return in; } @@ -897,13 +904,10 @@ format_pid (char in[INT_STRLEN_BOUND (pid_t)], pid_t pid) Finally, use REGS to add the required interpreter arguments to the caller's argv. - NAME must be a null-terminated string in a buffer of size PATH_MAX. - It might be updated to be a string no longer than PATH_MAX - 1. - Value is NULL upon failure, with errno set accordingly. */ char * -exec_0 (char name[PATH_MAX], struct exec_tracee *tracee, +exec_0 (char *name, struct exec_tracee *tracee, size_t *size, USER_REGS_STRUCT *regs) { int fd, rc, i; @@ -912,13 +916,14 @@ exec_0 (char name[PATH_MAX], struct exec_tracee *tracee, program_header program; USER_WORD entry, program_entry, offset; USER_WORD header_offset; - ptrdiff_t nlen; USER_WORD name_len, aligned_len; struct exec_jump_command jump; /* This also encompasses !__LP64__. */ #if defined __mips__ && !defined MIPS_NABI int fpu_mode; #endif /* defined __mips__ && !defined MIPS_NABI */ + char buffer[80], buffer1[PATH_MAX + 80], *rewrite; + ssize_t link_size; size_t remaining; /* If the process is trying to run /proc/self/exe, make it run @@ -926,13 +931,8 @@ exec_0 (char name[PATH_MAX], struct exec_tracee *tracee, if (!strcmp (name, "/proc/self/exe") && tracee->exec_file) { - nlen = strnlen (tracee->exec_file, PATH_MAX); - if (PATH_MAX <= nlen) - { - errno = ENAMETOOLONG; - return NULL; - } - memcpy (name, tracee->exec_file, nlen + 1); + strncpy (name, tracee->exec_file, PATH_MAX - 1); + name[PATH_MAX] = '\0'; } else { @@ -940,45 +940,45 @@ exec_0 (char name[PATH_MAX], struct exec_tracee *tracee, cwd. Do not use sprintf at it is not reentrant and it mishandles results longer than INT_MAX. */ - nlen = strlen (name); - if (name[0] && name[0] != '/') { - char buffer[sizeof "/proc//cwd" + INT_STRLEN_BOUND (pid_t)]; - char buffer1[PATH_MAX]; + /* Clear both buffers. */ + memset (buffer, 0, sizeof buffer); + memset (buffer1, 0, sizeof buffer1); - /* Copy over "/proc/", the PID, and "/cwd". */ - char *rewrite = stpcpy (buffer, "/proc/"); + /* Copy over /proc, the PID, and /cwd/. */ + rewrite = stpcpy (buffer, "/proc/"); rewrite = format_pid (rewrite, tracee->pid); strcpy (rewrite, "/cwd"); /* Resolve this symbolic link. */ - ssize_t link_size = readlink (buffer, buffer1, sizeof buffer1); + link_size = readlink (buffer, buffer1, + PATH_MAX + 1); + if (link_size < 0) return NULL; - /* Check that the link is reasonable. */ + /* Check that the name is a reasonable size. */ - if (link_size == 0 || buffer1[0] != '/') - { - errno = EINVAL; - return NULL; - } - - ptrdiff_t link_len = link_size - (buffer1[link_size - 1] == '/'); - if (PATH_MAX <= link_len + 1 + nlen) + if (link_size > PATH_MAX) { + /* The name is too long. */ errno = ENAMETOOLONG; return NULL; } - /* Replace name with link contents, - then '/' if needed, then name. */ - memmove (name + link_len + 1, name, nlen + 1); - memcpy (name, buffer1, link_len); - name[link_len] = '/'; - nlen += link_len + 1; + /* Add a directory separator if necessary. */ + + if (!link_size || buffer1[link_size - 1] != '/') + buffer1[link_size] = '/', link_size++; + + rewrite = buffer1 + link_size; + remaining = buffer1 + sizeof buffer1 - rewrite - 1; + memcpy (rewrite, name, strnlen (name, remaining)); + + /* Replace name with buffer1. */ + strcpy (name, buffer1); } } @@ -1151,7 +1151,7 @@ exec_0 (char name[PATH_MAX], struct exec_tracee *tracee, loader_area_used += sizeof jump; /* Copy the length of NAME and NAME itself to the loader area. */ - name_len = nlen; + name_len = strlen (name); aligned_len = ((name_len + 1 + sizeof name_len - 1) & -sizeof name_len); if (sizeof loader_area - loader_area_used diff --git a/exec/trace.c b/exec/trace.c index d3d6f223eb8..da9ac96c6ff 100644 --- a/exec/trace.c +++ b/exec/trace.c @@ -732,7 +732,7 @@ check_signal (struct exec_tracee *tracee, int status) static int handle_exec (struct exec_tracee *tracee, USER_REGS_STRUCT *regs) { - char buffer[PATH_MAX], *area; + char buffer[PATH_MAX + 80], *area; USER_REGS_STRUCT original; size_t size, loader_size; USER_WORD loader; commit b72dcebdabfc3b7b28c9542633bd48b43bcc6365 Author: Pip Cet Date: Sun May 24 12:05:50 2026 +0000 Avoid crash in self-insert-command with non-ASCII auto-fill * src/cmds.c (internal_self_insert): If the autofill function changed the newline character we inserted, don't attempt to restore point. * test/src/cmds-tests.el (self-insert-nonascii-autofill): New. diff --git a/src/cmds.c b/src/cmds.c index e9dee5ed2e3..9ca9a6d28de 100644 --- a/src/cmds.c +++ b/src/cmds.c @@ -489,7 +489,7 @@ internal_self_insert (int c, EMACS_INT n) SET_PT_BOTH (PT - 1, PT_BYTE - 1); auto_fill_result = call0 (Qinternal_auto_fill); /* Test PT < ZV in case the auto-fill-function is strange. */ - if (c == '\n' && PT < ZV) + if (c == '\n' && PT < ZV && FETCH_BYTE (PT) == '\n') SET_PT_BOTH (PT + 1, PT_BYTE + 1); if (!NILP (auto_fill_result)) hairy = 2; diff --git a/test/src/cmds-tests.el b/test/src/cmds-tests.el index 8c0e4706e3c..2038c01942f 100644 --- a/test/src/cmds-tests.el +++ b/test/src/cmds-tests.el @@ -48,5 +48,22 @@ (self-insert-command 0 10) (should-not (equal pt 0))))) +(ert-deftest self-insert-nonascii-autofill () + "Test `self-insert-command' with a non-ASCII autofill function." + (with-temp-buffer + (let ((auto-fill-function + (lambda () + (delete-char 1) + (insert #x2000) + (forward-char -1)))) + (dotimes (_ 10) + (self-insert-command 1 10) + (goto-char 2) + (should (equal (point) 2)) + (should (equal (length (buffer-string)) 1)) + (should (equal (format "%S" (buffer-string)) + "\"\x2000\"")) + (delete-char -1))))) + (provide 'cmds-tests) ;;; cmds-tests.el ends here commit 7f8ac8bf6f04045a676543862098c47bbf732f9e Author: Pip Cet Date: Sun May 24 11:51:38 2026 +0000 Avoid crash in self-insert-command for peculiar arguments * src/cmds.c (internal_self_insert): Don't call auto-fill-function after inserting zero newlines. * test/src/cmds-tests.el (self-insert-zero-newlines): New. diff --git a/src/cmds.c b/src/cmds.c index 99e436e65c1..e9dee5ed2e3 100644 --- a/src/cmds.c +++ b/src/cmds.c @@ -477,7 +477,8 @@ internal_self_insert (int c, EMACS_INT n) if ((CHAR_TABLE_P (Vauto_fill_chars) ? !NILP (CHAR_TABLE_REF (Vauto_fill_chars, c)) : (c == ' ' || c == '\n')) - && !NILP (BVAR (current_buffer, auto_fill_function))) + && !NILP (BVAR (current_buffer, auto_fill_function)) + && n > 0) { Lisp_Object auto_fill_result; diff --git a/test/src/cmds-tests.el b/test/src/cmds-tests.el index a02c36868ca..8c0e4706e3c 100644 --- a/test/src/cmds-tests.el +++ b/test/src/cmds-tests.el @@ -40,5 +40,13 @@ (let ((shortage (forward-line (+ 2 most-positive-fixnum)))) (should (= shortage (1+ most-positive-fixnum)))))) +(ert-deftest self-insert-zero-newlines () + "Test `self-insert-command' with arguments which used to cause a crash." + (with-temp-buffer + (let* ((pt nil) + (auto-fill-function (lambda () (setq pt (point))))) + (self-insert-command 0 10) + (should-not (equal pt 0))))) + (provide 'cmds-tests) ;;; cmds-tests.el ends here commit 94dbab2fe45fdcdcefc3a19ecf6c2063eecc1df2 Author: Pip Cet Date: Sun May 24 10:11:33 2026 +0000 Fix 'do_casify_natnum' for events with all flags set * src/casefiddle.c (do_casify_natnum): Extend range a little, to cover character events with all modifier flags set. * test/src/casefiddle-tests.el (casefiddle-allflags): New test. diff --git a/src/casefiddle.c b/src/casefiddle.c index fb44081b215..162014d4716 100644 --- a/src/casefiddle.c +++ b/src/casefiddle.c @@ -253,7 +253,7 @@ do_casify_natnum (struct casing_context *ctx, Lisp_Object obj) /* If the character has higher bits set above the flags, return it unchanged. It is not a real character. */ - if (! (0 <= ch && ch <= flagbits)) + if (! (0 <= ch && ch <= flagbits + MAX_CHAR)) return obj; int flags = ch & flagbits; diff --git a/test/src/casefiddle-tests.el b/test/src/casefiddle-tests.el index dcbe2e32c22..28895ec921e 100644 --- a/test/src/casefiddle-tests.el +++ b/test/src/casefiddle-tests.el @@ -306,4 +306,13 @@ (casefiddle-tests--check-syms "aa_bb cc_dd" "Aa_Bb Cc_Dd" "Aa_bb Cc_dd") (casefiddle-tests--check-syms "Aa_Bb Cc_Dd" "Aa_Bb Cc_Dd" "Aa_Bb Cc_Dd")) +(ert-deftest casefiddle-allflags () + "Check that all-flags events are properly handled by `upcase'." + ;; U+00FF LATIN SMALL LETTER Y WITH DIAERESIS + ;; U+0178 LATIN CAPITAL LETTER Y WITH DIAERESIS + (should (= (upcase ?\xff) + ?\x178)) + (should (= (upcase ?\A-\C-\H-\S-\s-\M-\xff) + ?\A-\C-\H-\S-\s-\M-\x178))) + ;;; casefiddle-tests.el ends here commit c146e3643c4eb2fc52fa730df9388544a31feb40 Author: Pip Cet Date: Sun May 24 09:17:44 2026 +0000 Fix off-by-one error in 'styled_format' This would (rarely) result in composition properties being shared across the concatenation of two copies of a string. * src/editfns.c (styled_format): Include the first argument in the range. * test/src/editfns-tests.el (editfns-tests--format-composition-property): New. diff --git a/src/editfns.c b/src/editfns.c index 341e241dfcb..4089edb1074 100644 --- a/src/editfns.c +++ b/src/editfns.c @@ -4398,7 +4398,7 @@ styled_format (ptrdiff_t nargs, Lisp_Object *args, bool message) props = extend_property_ranges (props, len, new_len); /* If successive arguments have properties, be sure that the value of `composition' property be the copy. */ - if (1 < i && info[i - 1].end) + if (1 <= i && info[i - 1].end) make_composition_value_copy (props); add_text_properties_from_list (val, props, make_fixnum (info[i].start)); diff --git a/test/src/editfns-tests.el b/test/src/editfns-tests.el index 9bdd5cf5db6..e6f80d0ef48 100644 --- a/test/src/editfns-tests.el +++ b/test/src/editfns-tests.el @@ -938,4 +938,13 @@ sufficiently large to avoid truncation." (pos-bol 2) (pos-eol 2)) (should (equal (buffer-string) "toto\nEmacs forever!\n")))) +(ert-deftest editfns-tests--format-composition-property () + "Check that composition properties are un-identified by `format'." + (let* ((s (compose-chars ?a ?b ?c)) + (str (format "%s%s%s" s s s))) + (should-not (eq (get-text-property 0 'composition str) + (get-text-property 3 'composition str))) + (should-not (eq (get-text-property 3 'composition str) + (get-text-property 6 'composition str))))) + ;;; editfns-tests.el ends here