Обсуждение: Pointer subtraction with a null pointer

Поиск
Список
Период
Сортировка

Pointer subtraction with a null pointer

От
Tom Lane
Дата:
Several of Andres' buildfarm animals have recently started to whine
that "performing pointer subtraction with a null pointer has undefined
behavior" for assorted places in freepage.c.

From a mathematical standpoint, this astonishes me: "x - 0 = x" is a
tautology.  So I'm a bit inclined to say "you're full of it" and disable
-Wnull-pointer-subtraction.  On the other hand, all of the occurrences
are in calls of relptr_store with a constant-NULL third argument.
So we could silence them without too much pain by adjusting that macro
to special-case NULL.  Or maybe we should change these call sites to do
something different, because this is surely abusing the intent of
relptr_store.

Thoughts?

            regards, tom lane



Re: Pointer subtraction with a null pointer

От
Tom Lane
Дата:
I wrote:
> Several of Andres' buildfarm animals have recently started to whine
> that "performing pointer subtraction with a null pointer has undefined
> behavior" for assorted places in freepage.c.

Ah, sorry, I meant to include a link:

https://buildfarm.postgresql.org/cgi-bin/show_stage_log.pl?nm=mylodon&dt=2022-03-26%2000%3A02%3A10&stg=make

This code is old, but mylodon wasn't doing that a week ago, so
Andres must've updated the compiler and/or changed its options.
kestrel and olingo are reporting it too, but they're new.

            regards, tom lane



Re: Pointer subtraction with a null pointer

От
Andres Freund
Дата:
Hi,

On 2022-03-26 12:04:54 -0400, Tom Lane wrote:
> Several of Andres' buildfarm animals have recently started to whine
> that "performing pointer subtraction with a null pointer has undefined
> behavior" for assorted places in freepage.c.
>
> From a mathematical standpoint, this astonishes me: "x - 0 = x" is a
> tautology.

I don't think that's quite what the warning is warning about. The C standard
doesn't allow pointer arithmetic between arbitrary pointers, they have to be
to the same "object" (plus a trailing array element).

http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1548.pdf 6.5.6 Additive
operators, 8/9

  When two pointers are subtracted, both shall point to elements of the same array object,
  or one past the last element of the array object; the result is the difference of the
  subscripts of the two array elements.

NULL can never be part of the same "array object" or one past past the last
element as the pointer it is subtracted from. Hence the undefined beaviour.


> Or maybe we should change these call sites to do something different,
> because this is surely abusing the intent of relptr_store.

I think a relptr_zero(), relptr_setnull() or such would make sense. That'd get
rid of the need for the cast as well.

Greetings,

Andres Freund



Re: Pointer subtraction with a null pointer

От
Isaac Morland
Дата:
On Sat, 26 Mar 2022 at 12:24, Andres Freund <andres@anarazel.de> wrote:
 
NULL can never be part of the same "array object" or one past past the last
element as the pointer it is subtracted from. Hence the undefined beaviour.

Even more fundamentally, NULL is not 0 in any ordinary mathematical sense, even though it can be written 0 in source code and is often (but not always) represented in memory as an all-0s bit pattern. I'm not at all surprised to learn that arithmetic involving NULL is undefined.

Re: Pointer subtraction with a null pointer

От
Andres Freund
Дата:
Hi,

On 2022-03-26 12:13:12 -0400, Tom Lane wrote:
> This code is old, but mylodon wasn't doing that a week ago, so
> Andres must've updated the compiler and/or changed its options.

Yep, updated it to clang 13. It's a warning present in 13, but not in 12.

I'll update it to 14 soon, now that that's released. It still has that
warning, so it's not going to help us avoid the warning.

Greetings,

Andres Freund



Re: Pointer subtraction with a null pointer

От
Tom Lane
Дата:
Andres Freund <andres@anarazel.de> writes:
> On 2022-03-26 12:13:12 -0400, Tom Lane wrote:
>> This code is old, but mylodon wasn't doing that a week ago, so
>> Andres must've updated the compiler and/or changed its options.

> Yep, updated it to clang 13. It's a warning present in 13, but not in 12.

OK, that answers that.

After more thought I agree that replacing these relptr_store calls
with something else would be the better solution.  I'll prepare a
patch.

            regards, tom lane



Re: Pointer subtraction with a null pointer

От
Tom Lane
Дата:
I wrote:
> Andres Freund <andres@anarazel.de> writes:
>> On 2022-03-26 12:13:12 -0400, Tom Lane wrote:
>>> This code is old, but mylodon wasn't doing that a week ago, so
>>> Andres must've updated the compiler and/or changed its options.

>> Yep, updated it to clang 13. It's a warning present in 13, but not in 12.

> OK, that answers that.

... Actually, after looking closer, I misread what our code is doing.
These call sites are trying to set the relptr value to "null" (zero),
and AFAICS it should be allowed:

freepage.c:188:2: warning: performing pointer subtraction with a null pointer has undefined behavior
[-Wnull-pointer-subtraction]
        relptr_store(base, fpm->btree_root, (FreePageBtree *) NULL);
        ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
../../../../src/include/utils/relptr.h:63:59: note: expanded from macro 'relptr_store'
         (rp).relptr_off = ((val) == NULL ? 0 : ((char *) (val)) - (base)))
                                                ~~~~~~~~~~~~~~~~ ^

clang is complaining about the subtraction despite it being inside
a conditional arm that cannot be reached when val is null.  It's hard
to see how that isn't a flat-out compiler bug.

However, granting that it isn't going to get fixed right away,
we could replace these call sites with "relptr_store_null()",
and maybe get rid of the conditional in relptr_store().

            regards, tom lane



Re: Pointer subtraction with a null pointer

От
Andres Freund
Дата:
Hi,

On 2022-03-26 13:23:34 -0400, Tom Lane wrote:
> I wrote:
> > Andres Freund <andres@anarazel.de> writes:
> >> On 2022-03-26 12:13:12 -0400, Tom Lane wrote:
> >>> This code is old, but mylodon wasn't doing that a week ago, so
> >>> Andres must've updated the compiler and/or changed its options.
>
> >> Yep, updated it to clang 13. It's a warning present in 13, but not in 12.
>
> > OK, that answers that.
>
> ... Actually, after looking closer, I misread what our code is doing.
> These call sites are trying to set the relptr value to "null" (zero),
> and AFAICS it should be allowed:
>
> freepage.c:188:2: warning: performing pointer subtraction with a null pointer has undefined behavior
[-Wnull-pointer-subtraction]
>         relptr_store(base, fpm->btree_root, (FreePageBtree *) NULL);
>         ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
> ../../../../src/include/utils/relptr.h:63:59: note: expanded from macro 'relptr_store'
>          (rp).relptr_off = ((val) == NULL ? 0 : ((char *) (val)) - (base)))
>                                                 ~~~~~~~~~~~~~~~~ ^
>
> clang is complaining about the subtraction despite it being inside
> a conditional arm that cannot be reached when val is null.

Huh, yea. I somehow read the conditional branch as guarding against a an
uninitialized base pointer or such.


> It's hard to see how that isn't a flat-out compiler bug.

It only happens if the NULL is directly passed as an argument to the macro,
not if there's an intermediary variable. Argh.


#include <stddef.h>

#define relptr_store(base, rp, val) \
     ((rp).relptr_off = ((val) == NULL ? 0 : ((char *) (val)) - (base)))

typedef union { struct foo *relptr_type; size_t relptr_off; } relptr;

void
problem_not_present(relptr *rp, char *base)
{
    struct foo *val = NULL;

    relptr_store(base, *rp, val);
}

void
problem_present(relptr *rp, char *base)
{
    relptr_store(base, *rp, NULL);
}


Looks like that warning is uttered whenever there's a subtraction from a
pointer with NULL, even if the code isn't reachable. Which I guess makes
*some* sense, outside of macros it's not something that'd ever be reasonable.


Wonder if we should try to get rid of the problem by also fixing the double
evaluation of val? I think something like

static inline void
relptr_store_internal(size_t *off, char *base, char *val)
{
    if (val == NULL)
        *off = 0;
    else
        *off = val - base;
}

#ifdef HAVE__BUILTIN_TYPES_COMPATIBLE_P
#define relptr_store(base, rp, val) \
    (AssertVariableIsOfTypeMacro(base, char *), \
     AssertVariableIsOfTypeMacro(val, __typeof__((rp).relptr_type)), \
         relptr_store_internal(&(rp).relptr_off, base, (char *) val))
#else
...

should do the trick?

Might also be worth adding an assertion that base < val.


> However, granting that it isn't going to get fixed right away,
> we could replace these call sites with "relptr_store_null()",
> and maybe get rid of the conditional in relptr_store().

Also would be good with that.

Greetings,

Andres Freund



Re: Pointer subtraction with a null pointer

От
Tom Lane
Дата:
I wrote:
> clang is complaining about the subtraction despite it being inside
> a conditional arm that cannot be reached when val is null.  It's hard
> to see how that isn't a flat-out compiler bug.
> However, granting that it isn't going to get fixed right away,
> we could replace these call sites with "relptr_store_null()",
> and maybe get rid of the conditional in relptr_store().

I've confirmed that the attached silences the warning with clang
13.0.0 (on Fedora 35).  The store_null notation is not awful, perhaps;
it makes those lines shorter and more readable.

I'm a bit less enthused about removing the conditional in relptr_store,
as that forces re-introducing it at a couple of call sites.  Perhaps
we should leave relptr_store alone ... but then the reason for
relptr_store_null is hard to explain except as a workaround for a
broken compiler.

I changed the comment suggesting that you could use relptrs with the
"process address space" as a base, because that would presumably mean
base == NULL which is going to draw the same warning.

            regards, tom lane

diff --git a/src/backend/utils/mmgr/freepage.c b/src/backend/utils/mmgr/freepage.c
index b26a295a4e..7af8ec2283 100644
--- a/src/backend/utils/mmgr/freepage.c
+++ b/src/backend/utils/mmgr/freepage.c
@@ -185,8 +185,8 @@ FreePageManagerInitialize(FreePageManager *fpm, char *base)
     Size        f;

     relptr_store(base, fpm->self, fpm);
-    relptr_store(base, fpm->btree_root, (FreePageBtree *) NULL);
-    relptr_store(base, fpm->btree_recycle, (FreePageSpanLeader *) NULL);
+    relptr_store_null(base, fpm->btree_root);
+    relptr_store_null(base, fpm->btree_recycle);
     fpm->btree_depth = 0;
     fpm->btree_recycle_count = 0;
     fpm->singleton_first_page = 0;
@@ -198,7 +198,7 @@ FreePageManagerInitialize(FreePageManager *fpm, char *base)
 #endif

     for (f = 0; f < FPM_NUM_FREELISTS; f++)
-        relptr_store(base, fpm->freelist[f], (FreePageSpanLeader *) NULL);
+        relptr_store_null(base, fpm->freelist[f]);
 }

 /*
@@ -596,7 +596,7 @@ FreePageBtreeCleanup(FreePageManager *fpm)
             if (root->hdr.magic == FREE_PAGE_LEAF_MAGIC)
             {
                 /* If root is a leaf, convert only entry to singleton range. */
-                relptr_store(base, fpm->btree_root, (FreePageBtree *) NULL);
+                relptr_store_null(base, fpm->btree_root);
                 fpm->singleton_first_page = root->u.leaf_key[0].first_page;
                 fpm->singleton_npages = root->u.leaf_key[0].npages;
             }
@@ -608,7 +608,7 @@ FreePageBtreeCleanup(FreePageManager *fpm)
                 Assert(root->hdr.magic == FREE_PAGE_INTERNAL_MAGIC);
                 relptr_copy(fpm->btree_root, root->u.internal_key[0].child);
                 newroot = relptr_access(base, fpm->btree_root);
-                relptr_store(base, newroot->hdr.parent, (FreePageBtree *) NULL);
+                relptr_store_null(base, newroot->hdr.parent);
             }
             FreePageBtreeRecycle(fpm, fpm_pointer_to_page(base, root));
         }
@@ -634,8 +634,7 @@ FreePageBtreeCleanup(FreePageManager *fpm)
                     fpm->singleton_npages = root->u.leaf_key[0].npages +
                         root->u.leaf_key[1].npages + 1;
                     fpm->btree_depth = 0;
-                    relptr_store(base, fpm->btree_root,
-                                 (FreePageBtree *) NULL);
+                    relptr_store_null(base, fpm->btree_root);
                     FreePagePushSpanLeader(fpm, fpm->singleton_first_page,
                                            fpm->singleton_npages);
                     Assert(max_contiguous_pages == 0);
@@ -886,8 +885,12 @@ FreePageBtreeGetRecycled(FreePageManager *fpm)
     Assert(victim != NULL);
     newhead = relptr_access(base, victim->next);
     if (newhead != NULL)
+    {
         relptr_copy(newhead->prev, victim->prev);
-    relptr_store(base, fpm->btree_recycle, newhead);
+        relptr_store(base, fpm->btree_recycle, newhead);
+    }
+    else
+        relptr_store_null(base, fpm->btree_recycle);
     Assert(fpm_pointer_is_page_aligned(base, victim));
     fpm->btree_recycle_count--;
     return (FreePageBtree *) victim;
@@ -940,8 +943,11 @@ FreePageBtreeRecycle(FreePageManager *fpm, Size pageno)
     span = (FreePageSpanLeader *) fpm_page_to_pointer(base, pageno);
     span->magic = FREE_PAGE_SPAN_LEADER_MAGIC;
     span->npages = 1;
-    relptr_store(base, span->next, head);
-    relptr_store(base, span->prev, (FreePageSpanLeader *) NULL);
+    if (head != NULL)
+        relptr_store(base, span->next, head);
+    else
+        relptr_store_null(base, span->next);
+    relptr_store_null(base, span->prev);
     if (head != NULL)
         relptr_store(base, head->prev, span);
     relptr_store(base, fpm->btree_recycle, span);
@@ -998,7 +1004,7 @@ FreePageBtreeRemovePage(FreePageManager *fpm, FreePageBtree *btp)
         if (parent == NULL)
         {
             /* We are removing the root page. */
-            relptr_store(base, fpm->btree_root, (FreePageBtree *) NULL);
+            relptr_store_null(base, fpm->btree_root);
             fpm->btree_depth = 0;
             Assert(fpm->singleton_first_page == 0);
             Assert(fpm->singleton_npages == 0);
@@ -1537,7 +1543,7 @@ FreePageManagerPutInternal(FreePageManager *fpm, Size first_page, Size npages,
             /* Create the btree and move the preexisting range into it. */
             root->hdr.magic = FREE_PAGE_LEAF_MAGIC;
             root->hdr.nused = 1;
-            relptr_store(base, root->hdr.parent, (FreePageBtree *) NULL);
+            relptr_store_null(base, root->hdr.parent);
             root->u.leaf_key[0].first_page = fpm->singleton_first_page;
             root->u.leaf_key[0].npages = fpm->singleton_npages;
             relptr_store(base, fpm->btree_root, root);
@@ -1773,8 +1779,7 @@ FreePageManagerPutInternal(FreePageManager *fpm, Size first_page, Size npages,
                     newroot = FreePageBtreeGetRecycled(fpm);
                     newroot->hdr.magic = FREE_PAGE_INTERNAL_MAGIC;
                     newroot->hdr.nused = 2;
-                    relptr_store(base, newroot->hdr.parent,
-                                 (FreePageBtree *) NULL);
+                    relptr_store_null(base, newroot->hdr.parent);
                     newroot->u.internal_key[0].first_page =
                         FreePageBtreeFirstKey(split_target);
                     relptr_store(base, newroot->u.internal_key[0].child,
@@ -1878,8 +1883,11 @@ FreePagePushSpanLeader(FreePageManager *fpm, Size first_page, Size npages)
     span = (FreePageSpanLeader *) fpm_page_to_pointer(base, first_page);
     span->magic = FREE_PAGE_SPAN_LEADER_MAGIC;
     span->npages = npages;
-    relptr_store(base, span->next, head);
-    relptr_store(base, span->prev, (FreePageSpanLeader *) NULL);
+    if (head != NULL)
+        relptr_store(base, span->next, head);
+    else
+        relptr_store_null(base, span->next);
+    relptr_store_null(base, span->prev);
     if (head != NULL)
         relptr_store(base, head->prev, span);
     relptr_store(base, fpm->freelist[f], span);
diff --git a/src/include/utils/relptr.h b/src/include/utils/relptr.h
index fdc2124d2c..15e47467a7 100644
--- a/src/include/utils/relptr.h
+++ b/src/include/utils/relptr.h
@@ -15,13 +15,16 @@
 #define RELPTR_H

 /*
- * Relative pointers are intended to be used when storing an address that may
- * be relative either to the base of the process's address space or some
- * dynamic shared memory segment mapped therein.
+ * Relative pointers are intended to be used when storing an address that
+ * is relative to the start of a dynamic memory segment, so that it can
+ * be interpreted by different processes that have the DSM mapped at
+ * different places in their address space.
  *
  * The idea here is that you declare a relative pointer as relptr(type)
- * and then use relptr_access to dereference it and relptr_store to change
- * it.  The use of a union here is a hack, because what's stored in the
+ * and then use relptr_access to dereference it and relptr_store or
+ * relptr_store_null to change it.
+ *
+ * The use of a union here is a hack, because what's stored in the
  * relptr is always a Size, never an actual pointer.  But including a pointer
  * in the union allows us to use stupid macro tricks to provide some measure
  * of type-safety.
@@ -56,11 +59,16 @@
 #define relptr_is_null(rp) \
     ((rp).relptr_off == 0)

+#define relptr_store_null(base, rp) \
+    (AssertVariableIsOfTypeMacro(base, char *), \
+     (rp).relptr_off = 0)
+
 #ifdef HAVE__BUILTIN_TYPES_COMPATIBLE_P
 #define relptr_store(base, rp, val) \
     (AssertVariableIsOfTypeMacro(base, char *), \
      AssertVariableIsOfTypeMacro(val, __typeof__((rp).relptr_type)), \
-     (rp).relptr_off = ((val) == NULL ? 0 : ((char *) (val)) - (base)))
+     AssertMacro((val) != NULL), \
+     (rp).relptr_off = ((char *) (val)) - (base))
 #else
 /*
  * If we don't have __builtin_types_compatible_p, assume we might not have
@@ -68,7 +76,8 @@
  */
 #define relptr_store(base, rp, val) \
     (AssertVariableIsOfTypeMacro(base, char *), \
-     (rp).relptr_off = ((val) == NULL ? 0 : ((char *) (val)) - (base)))
+     AssertMacro((val) != NULL), \
+     (rp).relptr_off = ((char *) (val)) - (base))
 #endif

 #define relptr_copy(rp1, rp2) \

Re: Pointer subtraction with a null pointer

От
Andres Freund
Дата:
Hi,

On 2022-03-26 10:49:53 -0700, Andres Freund wrote:
> > It's hard to see how that isn't a flat-out compiler bug.
> 
> It only happens if the NULL is directly passed as an argument to the macro,
> not if there's an intermediary variable. Argh.
> 
> 
> #include <stddef.h>
> 
> #define relptr_store(base, rp, val) \
>      ((rp).relptr_off = ((val) == NULL ? 0 : ((char *) (val)) - (base)))
> 
> typedef union { struct foo *relptr_type; size_t relptr_off; } relptr;
> 
> void
> problem_not_present(relptr *rp, char *base)
> {
>     struct foo *val = NULL;
> 
>     relptr_store(base, *rp, val);
> }
> 
> void
> problem_present(relptr *rp, char *base)
> {
>     relptr_store(base, *rp, NULL);
> }
> 
> 
> Looks like that warning is uttered whenever there's a subtraction from a
> pointer with NULL, even if the code isn't reachable. Which I guess makes
> *some* sense, outside of macros it's not something that'd ever be reasonable.

Reported as https://github.com/llvm/llvm-project/issues/54570

Greetings,

Andres Freund



Re: Pointer subtraction with a null pointer

От
Tom Lane
Дата:
Andres Freund <andres@anarazel.de> writes:
> Wonder if we should try to get rid of the problem by also fixing the double
> evaluation of val? I think something like

Good idea.  The attached also silences the warning, and getting rid
of the double-eval hazard seems like a net win.

> Might also be worth adding an assertion that base < val.

Did that too.  On the whole I like this better.

            regards, tom lane

diff --git a/src/include/utils/relptr.h b/src/include/utils/relptr.h
index fdc2124d2c..cc80a7200d 100644
--- a/src/include/utils/relptr.h
+++ b/src/include/utils/relptr.h
@@ -56,11 +56,24 @@
 #define relptr_is_null(rp) \
     ((rp).relptr_off == 0)
 
+/* We use this inline to avoid double eval of "val" in relptr_store */
+static inline Size
+relptr_store_eval(char *base, char *val)
+{
+    if (val == NULL)
+        return 0;
+    else
+    {
+        Assert(val > base);
+        return val - base;
+    }
+}
+
 #ifdef HAVE__BUILTIN_TYPES_COMPATIBLE_P
 #define relptr_store(base, rp, val) \
     (AssertVariableIsOfTypeMacro(base, char *), \
      AssertVariableIsOfTypeMacro(val, __typeof__((rp).relptr_type)), \
-     (rp).relptr_off = ((val) == NULL ? 0 : ((char *) (val)) - (base)))
+     (rp).relptr_off = relptr_store_eval(base, (char *) (val)))
 #else
 /*
  * If we don't have __builtin_types_compatible_p, assume we might not have
@@ -68,7 +81,7 @@
  */
 #define relptr_store(base, rp, val) \
     (AssertVariableIsOfTypeMacro(base, char *), \
-     (rp).relptr_off = ((val) == NULL ? 0 : ((char *) (val)) - (base)))
+     (rp).relptr_off = relptr_store_eval(base, (char *) (val)))
 #endif
 
 #define relptr_copy(rp1, rp2) \

Re: Pointer subtraction with a null pointer

От
Andres Freund
Дата:
Hi,

On 2022-03-26 14:13:56 -0400, Tom Lane wrote:
> The attached also silences the warning, and getting rid of the double-eval
> hazard seems like a net win.

Looks good wrt relptr_store. Maybe we should fix the double-eval hazard in
relptr_access too, think that'd be only one left over...


> > Might also be worth adding an assertion that base < val.
> 
> Did that too.  On the whole I like this better.

Better than the relptr_store_null() approach I assume? Agreed, if so.

Greetings,

Andres Freund



Re: Pointer subtraction with a null pointer

От
Tom Lane
Дата:
Andres Freund <andres@anarazel.de> writes:
> Looks good wrt relptr_store. Maybe we should fix the double-eval hazard in
> relptr_access too, think that'd be only one left over...

Hm.  Probably not worth the trouble, because it's hard to envision
a situation where rp is not a plain lvalue.

            regards, tom lane



Re: Pointer subtraction with a null pointer

От
Andres Freund
Дата:
On 2022-03-26 11:08:59 -0700, Andres Freund wrote:
> On 2022-03-26 10:49:53 -0700, Andres Freund wrote:
> > > It's hard to see how that isn't a flat-out compiler bug.
> >
> > It only happens if the NULL is directly passed as an argument to the macro,
> > not if there's an intermediary variable. Argh.
> >
> >
> > #include <stddef.h>
> >
> > #define relptr_store(base, rp, val) \
> >      ((rp).relptr_off = ((val) == NULL ? 0 : ((char *) (val)) - (base)))
> >
> > typedef union { struct foo *relptr_type; size_t relptr_off; } relptr;
> >
> > void
> > problem_not_present(relptr *rp, char *base)
> > {
> >     struct foo *val = NULL;
> >
> >     relptr_store(base, *rp, val);
> > }
> >
> > void
> > problem_present(relptr *rp, char *base)
> > {
> >     relptr_store(base, *rp, NULL);
> > }
> >
> >
> > Looks like that warning is uttered whenever there's a subtraction from a
> > pointer with NULL, even if the code isn't reachable. Which I guess makes
> > *some* sense, outside of macros it's not something that'd ever be reasonable.
>
> Reported as https://github.com/llvm/llvm-project/issues/54570

And it now got fixed. Will obviously be a bit till it reaches a compiler near
you...