radixtree: Fix SIGSEGV at update of embeddable value to non-embeddable.

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
This commit is contained in:
Masahiko Sawada 2024-04-25 21:48:52 +09:00
parent e03ec4641d
commit bb7f195ff7
4 changed files with 148 additions and 10 deletions

View 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;

View 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);

View 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);

View 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++)
{