radixtree: Fix SIGSEGV at update of embeddable value to non-embeddable.
authorMasahiko Sawada <[email protected]>
Thu, 25 Apr 2024 12:48:52 +0000 (21:48 +0900)
committerMasahiko Sawada <[email protected]>
Thu, 25 Apr 2024 12:48:52 +0000 (21:48 +0900)
Also, fix a memory leak when updating from non-embeddable to
embeddable. Both were unreachable without adding C code.

Reported-by: Noah Misch
Author: Noah Misch
Reviewed-by: Masahiko Sawada, John Naylor
Discussion: https://postgr.es/m/20240424210319.4c.nmisch%40google.com

src/include/lib/radixtree.h
src/test/modules/test_tidstore/expected/test_tidstore.out
src/test/modules/test_tidstore/sql/test_tidstore.sql
src/test/modules/test_tidstore/test_tidstore.c

index d9f545d491a0415eed4d614b3a20ad6b956c99f5..2896a6efc52170e9cb4da16ba7203b7313a142c7 100644 (file)
@@ -1749,6 +1749,10 @@ have_slot:
 
    if (RT_VALUE_IS_EMBEDDABLE(value_p))
    {
+       /* free the existing leaf */
+       if (found && !RT_CHILDPTR_IS_VALUE(*slot))
+           RT_FREE_LEAF(tree, *slot);
+
        /* store value directly in child pointer slot */
        memcpy(slot, value_p, value_sz);
 
@@ -1765,7 +1769,7 @@ have_slot:
    {
        RT_CHILD_PTR leaf;
 
-       if (found)
+       if (found && !RT_CHILDPTR_IS_VALUE(*slot))
        {
            Assert(RT_PTR_ALLOC_IS_VALID(*slot));
            leaf.alloc = *slot;
index 06c610e84c2471d5048f624981165ccc04c6d7d1..cbcacfd26e178d0ced36cc94d460271571c49fdb 100644 (file)
@@ -1,7 +1,3 @@
--- Note: The test code use an array of TIDs for verification similar
--- to vacuum's dead item array pre-PG17. To avoid adding duplicates,
--- each call to do_set_block_offsets() should use different block
--- numbers.
 CREATE EXTENSION test_tidstore;
 -- To hide the output of do_set_block_offsets()
 CREATE TEMP TABLE hideblocks(blockno bigint);
@@ -79,6 +75,118 @@ SELECT test_destroy();
  
 (1 row)
 
+-- Test replacements crossing RT_CHILDPTR_IS_VALUE in both directions
+SELECT test_create(false);
+ test_create 
+-------------
+(1 row)
+
+SELECT do_set_block_offsets(1, array[1]::int2[]); SELECT check_set_block_offsets();
+ do_set_block_offsets 
+----------------------
+                    1
+(1 row)
+
+ check_set_block_offsets 
+-------------------------
+(1 row)
+
+SELECT do_set_block_offsets(1, array[1,2]::int2[]); SELECT check_set_block_offsets();
+ do_set_block_offsets 
+----------------------
+                    1
+(1 row)
+
+ check_set_block_offsets 
+-------------------------
+(1 row)
+
+SELECT do_set_block_offsets(1, array[1,2,3]::int2[]); SELECT check_set_block_offsets();
+ do_set_block_offsets 
+----------------------
+                    1
+(1 row)
+
+ check_set_block_offsets 
+-------------------------
+(1 row)
+
+SELECT do_set_block_offsets(1, array[1,2,3,4]::int2[]); SELECT check_set_block_offsets();
+ do_set_block_offsets 
+----------------------
+                    1
+(1 row)
+
+ check_set_block_offsets 
+-------------------------
+(1 row)
+
+SELECT do_set_block_offsets(1, array[1,2,3,4,100]::int2[]); SELECT check_set_block_offsets();
+ do_set_block_offsets 
+----------------------
+                    1
+(1 row)
+
+ check_set_block_offsets 
+-------------------------
+(1 row)
+
+SELECT do_set_block_offsets(1, array[1,2,3,4]::int2[]); SELECT check_set_block_offsets();
+ do_set_block_offsets 
+----------------------
+                    1
+(1 row)
+
+ check_set_block_offsets 
+-------------------------
+(1 row)
+
+SELECT do_set_block_offsets(1, array[1,2,3]::int2[]); SELECT check_set_block_offsets();
+ do_set_block_offsets 
+----------------------
+                    1
+(1 row)
+
+ check_set_block_offsets 
+-------------------------
+(1 row)
+
+SELECT do_set_block_offsets(1, array[1,2]::int2[]); SELECT check_set_block_offsets();
+ do_set_block_offsets 
+----------------------
+                    1
+(1 row)
+
+ check_set_block_offsets 
+-------------------------
+(1 row)
+
+SELECT do_set_block_offsets(1, array[1]::int2[]); SELECT check_set_block_offsets();
+ do_set_block_offsets 
+----------------------
+                    1
+(1 row)
+
+ check_set_block_offsets 
+-------------------------
+(1 row)
+
+SELECT test_destroy();
+ test_destroy 
+--------------
+(1 row)
+
 -- Use shared memory this time. We can't do that in test_radixtree.sql,
 -- because unused static functions would raise warnings there.
 SELECT test_create(true);
index bb31877b9a675751b0ad191605ea62173464e675..a29e4ec1c55aa880426103d65cf79417ff0bbff2 100644 (file)
@@ -1,8 +1,3 @@
--- Note: The test code use an array of TIDs for verification similar
--- to vacuum's dead item array pre-PG17. To avoid adding duplicates,
--- each call to do_set_block_offsets() should use different block
--- numbers.
-
 CREATE EXTENSION test_tidstore;
 
 -- To hide the output of do_set_block_offsets()
@@ -50,6 +45,22 @@ SELECT test_is_full();
 
 -- Re-create the TID store for randommized tests.
 SELECT test_destroy();
+
+
+-- Test replacements crossing RT_CHILDPTR_IS_VALUE in both directions
+SELECT test_create(false);
+SELECT do_set_block_offsets(1, array[1]::int2[]); SELECT check_set_block_offsets();
+SELECT do_set_block_offsets(1, array[1,2]::int2[]); SELECT check_set_block_offsets();
+SELECT do_set_block_offsets(1, array[1,2,3]::int2[]); SELECT check_set_block_offsets();
+SELECT do_set_block_offsets(1, array[1,2,3,4]::int2[]); SELECT check_set_block_offsets();
+SELECT do_set_block_offsets(1, array[1,2,3,4,100]::int2[]); SELECT check_set_block_offsets();
+SELECT do_set_block_offsets(1, array[1,2,3,4]::int2[]); SELECT check_set_block_offsets();
+SELECT do_set_block_offsets(1, array[1,2,3]::int2[]); SELECT check_set_block_offsets();
+SELECT do_set_block_offsets(1, array[1,2]::int2[]); SELECT check_set_block_offsets();
+SELECT do_set_block_offsets(1, array[1]::int2[]); SELECT check_set_block_offsets();
+SELECT test_destroy();
+
+
 -- Use shared memory this time. We can't do that in test_radixtree.sql,
 -- because unused static functions would raise warnings there.
 SELECT test_create(true);
index 0a3a58722de8565c8fc93b5cbdfa7e02fa3ab5d0..5417163407bf1081db72812757d49879840e7f69 100644 (file)
@@ -146,6 +146,18 @@ sanity_check_array(ArrayType *ta)
                 errmsg("argument must be empty or one-dimensional array")));
 }
 
+static void
+purge_from_verification_array(BlockNumber blkno)
+{
+   int         dst = 0;
+
+   for (int src = 0; src < items.num_tids; src++)
+       if (ItemPointerGetBlockNumber(&items.insert_tids[src]) != blkno)
+           items.insert_tids[dst++] = items.insert_tids[src];
+   items.num_tids = dst;
+}
+
+
 /* Set the given block and offsets pairs */
 Datum
 do_set_block_offsets(PG_FUNCTION_ARGS)
@@ -165,6 +177,9 @@ do_set_block_offsets(PG_FUNCTION_ARGS)
    TidStoreSetBlockOffsets(tidstore, blkno, offs, noffs);
    TidStoreUnlock(tidstore);
 
+   /* Remove the existing items of blkno from the verification array */
+   purge_from_verification_array(blkno);
+
    /* Set TIDs in verification array */
    for (int i = 0; i < noffs; i++)
    {