patch 9.2.0196: textprop: negative IDs and can cause a crash

Commit: 
https://github.com/vim/vim/commit/0c109e4e6083df328f5fa8511ef0a936e934d310
Author: Hirohito Higashi <[email protected]>
Date:   Wed Mar 18 21:03:17 2026 +0000

    patch 9.2.0196: textprop: negative IDs and can cause a crash
    
    Problem:  textprop: negative IDs and can cause a crash without "text"
              (Paul Ollis)
    Solution: Strictly reserve negative IDs for virtual text, ignore "id"
              when "text" is provided in prop_add() (Hirohito Higashi).
    
    When prop_add() was called with a negative id and no "text", the
    property was stored with a negative tp_id.  A subsequent call to
    prop_list() or screen redraw would then treat it as a virtual text
    property and dereference b_textprop_text.ga_data, which is NULL when
    no virtual text properties exist.
    
    Negative ids are reserved for virtual text properties, so always
    reject them with E1293 regardless of whether virtual text properties
    exist.  Also, when "text" is specified any user-provided id is now
    silently ignored and an internal negative id is assigned.
    
    Remove the now-unnecessary did_use_negative_pop_id flag and E1339.
    Update E1293's message and the documentation accordingly.
    
    related: #19684
    closes:  #19741
    
    Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
    Signed-off-by: Hirohito Higashi <[email protected]>
    Signed-off-by: Christian Brabandt <[email protected]>

diff --git a/runtime/doc/tags b/runtime/doc/tags
index 5117c07ef..77b19ec7f 100644
--- a/runtime/doc/tags
+++ b/runtime/doc/tags
@@ -4573,7 +4573,6 @@ E1335     vim9class.txt   /*E1335*
 E1336  options.txt     /*E1336*
 E1337  vim9class.txt   /*E1337*
 E1338  vim9class.txt   /*E1338*
-E1339  textprop.txt    /*E1339*
 E134   change.txt      /*E134*
 E1340  vim9class.txt   /*E1340*
 E1341  vim9class.txt   /*E1341*
diff --git a/runtime/doc/textprop.txt b/runtime/doc/textprop.txt
index 3fa7d14a5..fbe4231c7 100644
--- a/runtime/doc/textprop.txt
+++ b/runtime/doc/textprop.txt
@@ -141,9 +141,9 @@ prop_add({lnum}, {col}, {props})
                                the current buffer is used
                   id           user defined ID for the property; must be a
                                number, should be positive |E1510|;
-                               when using "text" then "id" must not be
-                               present and will be set automatically to a
-                               negative number; otherwise zero is used
+                               when using "text" then any "id" value is
+                               ignored and a negative number is assigned
+                               automatically; otherwise zero is used
                                                        *E1305*
                   text         text to be displayed before {col}, or
                                above/below the line if {col} is zero; prepend
@@ -224,14 +224,9 @@ prop_add({lnum}, {col}, {props})
                is difficult to compute).
                A negative "id" will be chosen and is returned.
 
-               Before text properties with text were supported it was
-               possible to use a negative "id", even though this was very
-               rare.  Now that negative "id"s are reserved for text
-               properties with text an error is given when using a negative
-               "id".  When a text property with text already exists using a
-               negative "id" results in *E1293* .  If a negative "id" was
-               used and later a text property with text is added results in
-               *E1339* .
+               Negative "id"s are reserved for text properties with "text"
+               and cannot be used otherwise.  Using a negative "id" results
+               in *E1293* .
 
                Can also be used as a |method|: >
                        GetLnum()->prop_add(col, props)
diff --git a/src/errors.h b/src/errors.h
index 9c4e485ef..d780fb420 100644
--- a/src/errors.h
+++ b/src/errors.h
@@ -3328,8 +3328,8 @@ EXTERN char e_invalid_argument_nr[]
 EXTERN char e_cmdline_window_already_open[]
        INIT(= N_("E1292: Command-line window is already open"));
 #ifdef FEAT_PROP_POPUP
-EXTERN char e_cannot_use_negative_id_after_adding_textprop_with_text[]
-       INIT(= N_("E1293: Cannot use a negative id after adding a textprop with 
text"));
+EXTERN char e_cannot_use_negative_id[]
+       INIT(= N_("E1293: Cannot use a negative id for a text property"));
 EXTERN char e_can_only_use_text_align_when_column_is_zero[]
        INIT(= N_("E1294: Can only use text_align when column is zero"));
 #endif
@@ -3432,10 +3432,7 @@ EXTERN char e_class_variable_str_not_found_in_class_str[]
        INIT(= N_("E1337: Class variable \"%s\" not found in class \"%s\""));
 // E1338 unused
 #endif
-#ifdef FEAT_PROP_POPUP
-EXTERN char 
e_cannot_add_textprop_with_text_after_using_textprop_with_negative_id[]
-       INIT(= N_("E1339: Cannot add a textprop with text after using a 
textprop with a negative id"));
-#endif
+// E1339 unused
 #ifdef FEAT_EVAL
 EXTERN char e_argument_already_declared_in_class_str[]
        INIT(= N_("E1340: Argument already declared in the class: %s"));
diff --git a/src/po/vim.pot b/src/po/vim.pot
index 446a0b91f..d67bcafcc 100644
--- a/src/po/vim.pot
+++ b/src/po/vim.pot
@@ -8,7 +8,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: Vim
"
 "Report-Msgid-Bugs-To: [email protected]
"
-"POT-Creation-Date: 2026-03-13 18:26+0000
"
+"POT-Creation-Date: 2026-03-18 21:08+0000
"
 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE
"
 "Last-Translator: FULL NAME <EMAIL@ADDRESS>
"
 "Language-Team: LANGUAGE <[email protected]>
"
@@ -8099,7 +8099,7 @@ msgstr ""
 msgid "E1292: Command-line window is already open"
 msgstr ""
 
-msgid "E1293: Cannot use a negative id after adding a textprop with text"
+msgid "E1293: Cannot use a negative id for a text property"
 msgstr ""
 
 msgid "E1294: Can only use text_align when column is zero"
@@ -8243,11 +8243,6 @@ msgstr ""
 msgid "E1337: Class variable \"%s\" not found in class \"%s\""
 msgstr ""
 
-msgid ""
-"E1339: Cannot add a textprop with text after using a textprop with a "
-"negative id"
-msgstr ""
-
 #, c-format
 msgid "E1340: Argument already declared in the class: %s"
 msgstr ""
diff --git a/src/testdir/test_textprop.vim b/src/testdir/test_textprop.vim
index 975a5846a..f8323019b 100644
--- a/src/testdir/test_textprop.vim
+++ b/src/testdir/test_textprop.vim
@@ -4669,47 +4669,18 @@ endfunc
 
 func Test_error_when_using_negative_id()
   call prop_type_add('test1', #{highlight: 'ErrorMsg'})
-  call prop_add(1, 1, #{type: 'test1', text: 'virtual'})
-  call assert_fails("call prop_add(1, 1, #{type: 'test1', length: 1, id: 
-1})", 'E1293:')
-
-  call prop_type_delete('test1')
-endfunc
-
-func Test_error_after_using_negative_id()
-  CheckScreendump
-  " This needs to run a separate Vim instance because the
-  " "did_use_negative_pop_id" will be set.
-  CheckRunVimInTerminal
-
-  let lines =<< trim END
-      vim9script
 
-      setline(1, ['one', 'two', 'three'])
-      prop_type_add('test_1', {highlight: 'Error'})
-      prop_type_add('test_2', {highlight: 'WildMenu'})
-
-      prop_add(3, 1, {
-          type: 'test_1',
-          length: 5,
-          id: -1
-      })
-
-      def g:AddTextprop()
-          prop_add(1, 0, {
-              type: 'test_2',
-              text: 'The quick fox',
-              text_padding_left: 2
-          })
-      enddef
-  END
-  call writefile(lines, 'XtextPropError', 'D')
-  let buf = RunVimInTerminal('-S XtextPropError', #{rows: 8, cols: 60})
-  call VerifyScreenDump(buf, 'Test_prop_negative_error_1', {})
+  " Negative id is always rejected.  Before the fix, prop_add() with a negative
+  " id succeeded when no virtual text existed, then prop_list() would 
dereference
+  " a NULL pointer (b_textprop_text.ga_data) and crash.
+  call assert_fails("call prop_add(1, 1, #{type: 'test1', length: 1, id: 
-1})", 'E1293:')
+  call assert_equal([], prop_list(1))
 
-  call term_sendkeys(buf, ":call AddTextprop()\<CR>")
-  call VerifyScreenDump(buf, 'Test_prop_negative_error_2', {})
+  " id is silently ignored when text is also specified.
+  let propid = prop_add(1, 1, #{type: 'test1', text: 'virtual', id: 42})
+  call assert_true(propid < 0)
 
-  call StopVimInTerminal(buf)
+  call prop_type_delete('test1')
 endfunc
 
 func Test_modify_text_before_prop()
diff --git a/src/textprop.c b/src/textprop.c
index c2519e993..f0d418eee 100644
--- a/src/textprop.c
+++ b/src/textprop.c
@@ -434,10 +434,6 @@ get_textprop_id(buf_T *buf)
     return -(buf->b_textprop_text.ga_len + 1);
 }
 
-// Flag that is set when a negative ID isused for a normal text property.
-// It is then impossible to use virtual text properties.
-static int did_use_negative_pop_id = FALSE;
-
 /*
  * Shared between prop_add() and popup_create().
  * "dict_arg" is the function argument of a dict containing "bufnr".
@@ -599,24 +595,13 @@ prop_add_common(
     if (dict_arg != NULL && get_bufnr_from_arg(dict_arg, &buf) == FAIL)
        goto theend;
 
-    if (id < 0)
-    {
-       if (buf->b_textprop_text.ga_len > 0)
-       {
-           emsg(_(e_cannot_use_negative_id_after_adding_textprop_with_text));
-           goto theend;
-       }
-       did_use_negative_pop_id = TRUE;
-    }
-
     if (text != NULL)
-    {
-       if (did_use_negative_pop_id)
-       {
-           
emsg(_(e_cannot_add_textprop_with_text_after_using_textprop_with_negative_id));
-           goto theend;
-       }
+       // Always assign an internal negative id; ignore any user-provided id.
        id = get_textprop_id(buf);
+    else if (id < 0)
+    {
+       emsg(_(e_cannot_use_negative_id));
+       goto theend;
     }
 
     // This must be done _before_ we add the property because property changes
@@ -981,7 +966,11 @@ prop_fill_dict(dict_T *dict, textprop_T *prop, buf_T *buf)
 {
     proptype_T *pt;
     int buflocal = TRUE;
-    int virtualtext_prop = prop->tp_id < 0;
+    // A negative tp_id normally means a virtual text property, but a user
+    // may set a negative id for a regular property when no virtual text
+    // properties exist.  Guard against that by checking the index is valid.
+    int virtualtext_prop = prop->tp_id < 0
+                          && -prop->tp_id - 1 < buf->b_textprop_text.ga_len;
 
     dict_add_number(dict, "col", (prop->tp_col == MAXCOL) ? 0 : prop->tp_col);
     if (!virtualtext_prop)
diff --git a/src/version.c b/src/version.c
index 4a23daa86..73c466b41 100644
--- a/src/version.c
+++ b/src/version.c
@@ -734,6 +734,8 @@ static char *(features[]) =
 
 static int included_patches[] =
 {   /* Add new patch number below this line */
+/**/
+    196,
 /**/
     195,
 /**/

-- 
-- 
You received this message from the "vim_dev" maillist.
Do not top-post! Type your reply below the text you are replying to.
For more information, visit http://www.vim.org/maillist.php

--- 
You received this message because you are subscribed to the Google Groups 
"vim_dev" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to [email protected].
To view this discussion visit 
https://groups.google.com/d/msgid/vim_dev/E1w2yEJ-00C64E-LK%40256bit.org.

Raspunde prin e-mail lui