https://gcc.gnu.org/bugzilla/show_bug.cgi?id=81801

            Bug ID: 81801
           Summary: [PATCH] Difference of two pointers generates signed
                    overflow
           Product: gcc
           Version: 8.0
            Status: UNCONFIRMED
          Severity: normal
          Priority: P3
         Component: c
          Assignee: unassigned at gcc dot gnu.org
          Reporter: oss at malat dot biz
  Target Milestone: ---

Created attachment 41964
  --> https://gcc.gnu.org/bugzilla/attachment.cgi?id=41964&action=edit
Correction + test

SSH compiled with -ftrapv aborts in strlcpy() function, which computes a length
of a string by subtracting pointer to the string from the pointer to '\0'
character terminating the string.

The problem arises only if the string spawns over the positive/negative
boundary, for example assume a string of the length 23 on 32-bit platform
located at 0x7FFFfff8:
   char *str = (char *)0x7FFFfff8;
   char *str_end = (char *)0x8000000f;
then the following:
   int len = str_end - str;
produces a signed overflow, which leads to abort() if compiled with -ftrapv.

The whole issue can be easily shown by compiling the following code:
  int ptrdiff(char *a, char *b) {
    return b - a;
  }
but more illustrative is to use a pointer to a larger object:
  int ptrdiff(int *a, int *b) {
    return b - a;
  }
which produces the following test.c.003t.original:
  ;; Function ptrdiff (null)
  ;; enabled by -tree-original
  {
    return ((int) b - (int) a) /[ex] 4;
  }
I have tracked this problem down to pointer_diff() function, which explicitly
uses signed type - either ptrdiff_t or larger signed type matching the pointer
size if needed. I assume this is not correct and the pointer difference should
always use unsigned type matching the size of the pointer to make the operation
well behaving even if it overflows. After changing the function to use unsigned
(the patch is attached), I get the following test.c.003t.original:
  ;; Function ptrdiff (null)
  ;; enabled by -tree-original
  {
    return (int) ((unsigned int) b - (unsigned int) a) /[ex] 4;
  }
which looks better to me.

The problem with this change is that it leads to
FAIL: gcc.dg/tree-ssa/cmpexactdiv-2.c scan-tree-dump-not optimized "minus_expr"
where it doesn't optimize
  __PTRDIFF_TYPE__ l1 = b - a;
  __PTRDIFF_TYPE__ l2 = c - a;
  return l1 < l2;
to
  return b < c;
Which it could still do, but not because it knows the overflow can't occur but
because if the difference of two pointers doesn't fit to __PTRDIFF_TYPE__ the
behavior is undefined - so it's not possible the difference of two pointers
would wrap around to a wrong sign in the correct program.

Reply via email to