commit 5a83f23430bf9c06f15a7a5095dce8fc8d6e6e06
parent 18642a63be4f9c196f758cc365443231759faaae
Author: zeertzjq <zeertzjq@outlook.com>
Date: Fri, 12 Dec 2025 08:14:58 +0800
vim-patch:98a0cbf: patch 9.1.1971: crash with invalid positional argument 0 in printf() (#36919)
Problem: crash with invalid positional argument 0 in printf()
Solution: Reject positional arguments <= 0.
closes: vim/vim#18898
https://github.com/vim/vim/commit/98a0cbf05bd45fa6c4ede7b791fd21760bc587c8
Co-authored-by: Christian Brabandt <cb@256bit.org>
Diffstat:
5 files changed, 25 insertions(+), 18 deletions(-)
diff --git a/runtime/doc/vimfn.txt b/runtime/doc/vimfn.txt
@@ -7513,24 +7513,24 @@ printf({fmt}, {expr1} ...) *printf()*
<
*E1502*
You can re-use a [field-width] (or [precision]) argument: >vim
- echo printf("%1$d at width %2$d is: %01$*2$d", 1, 2)
+ echo printf("%1$d at width %2$d is: %1$0*2$d", 1, 2)
" 1 at width 2 is: 01
<
However, you can't use it as a different type: >vim
- echo printf("%1$d at width %2$ld is: %01$*2$d", 1, 2)
+ echo printf("%1$d at width %2$ld is: %1$0*2$d", 1, 2)
" E1502: Positional argument 2 used as field width reused as
" different type: long int/int
<
*E1503*
When a positional argument is used, but not the correct number
or arguments is given, an error is raised: >vim
- echo printf("%1$d at width %2$d is: %01$*2$.*3$d", 1, 2)
+ echo printf("%1$d at width %2$d is: %1$0*2$.*3$d", 1, 2)
" E1503: Positional argument 3 out of bounds: %1$d at width
- " %2$d is: %01$*2$.*3$d
+ " %2$d is: %1$0*2$.*3$d
<
Only the first error is reported: >vim
- echo printf("%01$*2$.*3$d %4$d", 1, 2)
- " E1503: Positional argument 3 out of bounds: %01$*2$.*3$d
+ echo printf("%1$0*2$.*3$d %4$d", 1, 2)
+ " E1503: Positional argument 3 out of bounds: %1$0*2$.*3$d
" %4$d
<
*E1504*
diff --git a/runtime/lua/vim/_meta/vimfn.lua b/runtime/lua/vim/_meta/vimfn.lua
@@ -6833,24 +6833,24 @@ function vim.fn.prevnonblank(lnum) end
--- <
--- *E1502*
--- You can re-use a [field-width] (or [precision]) argument: >vim
---- echo printf("%1$d at width %2$d is: %01$*2$d", 1, 2)
+--- echo printf("%1$d at width %2$d is: %1$0*2$d", 1, 2)
--- " 1 at width 2 is: 01
--- <
--- However, you can't use it as a different type: >vim
---- echo printf("%1$d at width %2$ld is: %01$*2$d", 1, 2)
+--- echo printf("%1$d at width %2$ld is: %1$0*2$d", 1, 2)
--- " E1502: Positional argument 2 used as field width reused as
--- " different type: long int/int
--- <
--- *E1503*
--- When a positional argument is used, but not the correct number
--- or arguments is given, an error is raised: >vim
---- echo printf("%1$d at width %2$d is: %01$*2$.*3$d", 1, 2)
+--- echo printf("%1$d at width %2$d is: %1$0*2$.*3$d", 1, 2)
--- " E1503: Positional argument 3 out of bounds: %1$d at width
---- " %2$d is: %01$*2$.*3$d
+--- " %2$d is: %1$0*2$.*3$d
--- <
--- Only the first error is reported: >vim
---- echo printf("%01$*2$.*3$d %4$d", 1, 2)
---- " E1503: Positional argument 3 out of bounds: %01$*2$.*3$d
+--- echo printf("%1$0*2$.*3$d %4$d", 1, 2)
+--- " E1503: Positional argument 3 out of bounds: %1$0*2$.*3$d
--- " %4$d
--- <
--- *E1504*
diff --git a/src/nvim/eval.lua b/src/nvim/eval.lua
@@ -8311,24 +8311,24 @@ M.funcs = {
<
*E1502*
You can re-use a [field-width] (or [precision]) argument: >vim
- echo printf("%1$d at width %2$d is: %01$*2$d", 1, 2)
+ echo printf("%1$d at width %2$d is: %1$0*2$d", 1, 2)
" 1 at width 2 is: 01
<
However, you can't use it as a different type: >vim
- echo printf("%1$d at width %2$ld is: %01$*2$d", 1, 2)
+ echo printf("%1$d at width %2$ld is: %1$0*2$d", 1, 2)
" E1502: Positional argument 2 used as field width reused as
" different type: long int/int
<
*E1503*
When a positional argument is used, but not the correct number
or arguments is given, an error is raised: >vim
- echo printf("%1$d at width %2$d is: %01$*2$.*3$d", 1, 2)
+ echo printf("%1$d at width %2$d is: %1$0*2$.*3$d", 1, 2)
" E1503: Positional argument 3 out of bounds: %1$d at width
- " %2$d is: %01$*2$.*3$d
+ " %2$d is: %1$0*2$.*3$d
<
Only the first error is reported: >vim
- echo printf("%01$*2$.*3$d %4$d", 1, 2)
- " E1503: Positional argument 3 out of bounds: %01$*2$.*3$d
+ echo printf("%1$0*2$.*3$d %4$d", 1, 2)
+ " E1503: Positional argument 3 out of bounds: %1$0*2$.*3$d
" %4$d
<
*E1504*
diff --git a/src/nvim/strings.c b/src/nvim/strings.c
@@ -989,6 +989,11 @@ static char *format_typename(const char *type)
static int adjust_types(const char ***ap_types, int arg, int *num_posarg, const char *type)
FUNC_ATTR_NONNULL_ALL
{
+ if (arg <= 0) {
+ semsg(_(e_invalid_format_specifier_str), type);
+ return FAIL;
+ }
+
if (*ap_types == NULL || *num_posarg < arg) {
const char **new_types = *ap_types == NULL
? xcalloc((size_t)arg, sizeof(const char *))
diff --git a/test/old/testdir/test_format.vim b/test/old/testdir/test_format.vim
@@ -300,6 +300,8 @@ func Test_printf_pos_errors()
call CheckLegacyAndVim9Failure(["call printf('%1$1$.5d', 5)"], "E1505:")
call CheckLegacyAndVim9Failure(["call printf('%1$5.1$d', 5)"], "E1505:")
call CheckLegacyAndVim9Failure(["call printf('%1$1$.1$d', 5)"], "E1505:")
+ call CheckLegacyAndVim9Failure(["call printf('%1$*1$.*0$s')"], "E1505:")
+ call CheckLegacyAndVim9Failure(["call printf('%*0$s')"], "E1505:")
call CheckLegacyAndVim9Failure(["call printf('%.123456789$d', 5)"], "E1510:")
call CheckLegacyAndVim9Failure(["call printf('%.123456789d', 5)"], "E1510:")