(root)/
gcc-13.2.0/
gcc/
testsuite/
c-c++-common/
Wstringop-truncation.c
       1  /* PR middle-end/81117 - Improve buffer overflow checking in strncpy
       2     { dg-do compile }
       3     { dg-options "-O2 -Wstringop-truncation -Wno-stringop-overflow -ftrack-macro-expansion=0" }
       4     { dg-require-effective-target alloca } */
       5  
       6  
       7  typedef __SIZE_TYPE__ size_t;
       8  
       9  #if __cplusplus
      10  extern "C" {
      11  #endif
      12  
      13  size_t strlen (const char*);
      14  char* strncat (char*, const char*, size_t);
      15  char* strncpy (char*, const char*, size_t);
      16  
      17  #if __cplusplus
      18  }
      19  #endif
      20  
      21  static size_t unsigned_value (void)
      22  {
      23    extern volatile size_t unsigned_value_source;
      24    return unsigned_value_source;
      25  }
      26  
      27  static size_t unsigned_range (size_t min, size_t max)
      28  {
      29    size_t val = unsigned_value ();
      30    return val < min || max < val ? min : val;
      31  }
      32  
      33  #define UR(min, max) unsigned_range (min, max)
      34  
      35  void sink (void*);
      36  
      37  #define S4 "123"
      38  const char a4[] = "123";
      39  
      40  #define CHOOSE(a, b) (unsigned_value () & 1 ? a : b)
      41  
      42  
      43  typedef struct Dest
      44  {
      45    char a5[5];
      46    char b7[7];
      47    char c3ns[3] __attribute__ ((nonstring));
      48  } Dest;
      49  
      50  char dst7[7];
      51  char dst2_5[2][5];
      52  
      53  /* Verify strncat warnings for arrays of known bounds.  */
      54  
      55  void test_strncat_array (Dest *pd)
      56  {
      57  #define CAT(d, s, len) (strncat ((d), (s), (len)), sink (d))
      58  
      59    CAT (dst7, S4, 2);                /* { dg-warning "output truncated copying 2 bytes from a string of length 3" } */
      60  
      61    CAT (dst7, a4, 1);                /* { dg-warning "output truncated copying 1 byte from a string of length 3" } */
      62  
      63    /* There is no truncation here but possible overflow so these
      64       are diagnosed by -Wstringop-overflow:
      65       CAT (dst7, S4, 3);
      66       CAT (dst7, a4, 3);
      67    */
      68  
      69    CAT (pd->a5, S4, 2);              /* { dg-warning "output truncated copying 2 bytes from a string of length 3" } */
      70    CAT (pd->a5, S4, 1);              /* { dg-warning "output truncated copying 1 byte from a string of length 3" } */
      71  }
      72  
      73  /* Verify strncat warnings for arrays of known bounds and a non-const
      74     character count in some range.  */
      75  
      76  void test_strncat_array_range (Dest *pd)
      77  {
      78    CAT (dst7, S4, UR (0, 1));        /* { dg-warning "output truncated copying between 0 and 1 bytes from a string of length 3" } */
      79    CAT (dst7, S4, UR (0, 2));        /* { dg-warning "output truncated copying between 0 and 2 bytes from a string of length 3" } */
      80    CAT (dst7, S4, UR (1, 3));        /* { dg-warning "output truncated copying between 1 and 3 bytes from a string of length 3" } */
      81    CAT (dst7, S4, UR (2, 4));        /* { dg-warning "output may be truncated copying between 2 and 4 bytes from a string of length 3" } */
      82  
      83    CAT (dst7, S4, UR (0, 7));
      84    CAT (dst7, S4, UR (1, 7));
      85    CAT (dst7, S4, UR (6, 7));
      86  
      87    CAT (dst7, S4, UR (0, 99));
      88  
      89    CAT (dst7, S4, UR (0, 99));
      90  }
      91  
      92  /* Verify strncat warnings for arrays of unknown bounds.  */
      93  
      94  void test_strncat_vla (char *d, unsigned n)
      95  {
      96    CAT (d, S4, 2);                   /* { dg-warning "output truncated copying 2 bytes from a string of length 3" } */
      97    CAT (d, S4, 4);
      98  
      99    CAT (d, a4, 2);                   /* { dg-warning "output truncated copying 2 bytes from a string of length 3" } */
     100  
     101    /* There is no truncation here but possible overflow so these
     102       are diagnosed by -Wstringop-overflow:
     103       CAT (d, S4, 3);
     104       CAT (d, a4, 3);
     105    */
     106    CAT (d, a4, 4);
     107  
     108    char vla[n];
     109  
     110    CAT (vla, S4, 2);                 /* { dg-warning "output truncated copying 2 bytes from a string of length 3" } */
     111  
     112    CAT (vla, S4, 4);
     113    CAT (vla, S4, n);
     114  
     115    CAT (vla, a4, 2);                 /* { dg-warning "output truncated copying 2 bytes from a string of length 3" } */
     116  
     117    CAT (vla, a4, 4);
     118    CAT (vla, a4, n);
     119  
     120    CAT (d, vla, 1);
     121    CAT (d, vla, 3);
     122    CAT (d, vla, 4);
     123    CAT (d, vla, n);
     124  
     125    /* There is no truncation here but possible overflow so these
     126       are diagnosed by -Wstringop-overflow:
     127    CAT (vla, S4, 3);
     128    CAT (vla, a4, 3);
     129    */
     130  }
     131  
     132  /* Verify strncpy warnings with at least one pointer to an object
     133     or string of unknown size (destination) or length (source).  */
     134  
     135  void test_strncpy_ptr (char *d, const char* s, const char *t, int i)
     136  {
     137  #define CPY(d, s, len) (strncpy ((d), (s), (len)), sink (d))
     138  
     139    /* Strncpy doesn't nul-terminate so the following is diagnosed.  */
     140    CPY (d, "",    0);                /* { dg-warning ".strncpy\[^\n\r\]* destination unchanged after copying no bytes" } */
     141    CPY (d, s,     0);                /* { dg-warning ".strncpy\[^\n\r\]* destination unchanged after copying no bytes" } */
     142  
     143    /* This is safe.  */
     144    CPY (d, "",    1);
     145    CPY (d, "",    2);
     146  
     147    /* This could be safe.  */
     148    CPY (d, s,     1);
     149    CPY (d, s,     2);
     150  
     151    /* Truncation.  */
     152    CPY (d, "123", 1);                /* { dg-warning ".strncpy\[^\n\r\]* output truncated copying 1 byte from a string of length 3" } */
     153    CPY (d, "123", 2);                /* { dg-warning ".strncpy\[^\n\r\]* output truncated copying 2 bytes from a string of length 3" } */
     154    CPY (d, "123", 3);                /* { dg-warning ".strncpy\[^\n\r\]* output truncated before terminating nul copying 3 bytes from a string of the same length" } */
     155    CPY (d, "123", 4);
     156    CPY (d, "123", 9);
     157  
     158    CPY (d, S4, sizeof S4);           /* Covered by -Wsizeof-pointer-memaccess.  */
     159    CPY (d, S4, sizeof S4 - 1);       /* { dg-warning ".strncpy\[^\n\r\]* output truncated before terminating nul copying 3 bytes from a string of the same length" } */
     160  
     161    CPY (d, a4, sizeof a4);           /* Covered by -Wsizeof-pointer-memaccess.  */
     162    CPY (d, a4, sizeof a4 - 1);       /* { dg-warning ".strncpy\[^\n\r\]* output truncated before terminating nul copying 3 bytes from a string of the same length" } */
     163    CPY (d, a4, sizeof a4 - 3);       /* { dg-warning ".strncpy\[^\n\r\]* output truncated copying 1 byte from a string of length 3" } */
     164    CPY (d, a4, sizeof a4 - 4);       /* { dg-warning ".strncpy\[^\n\r\]* destination unchanged after copying no bytes from a string of length 3" } */
     165  
     166    CPY (d, S4, strlen (S4));         /* { dg-warning ".strncpy\[^\n\r\]* output truncated before terminating nul copying 3 bytes from a string of the same length" } */
     167    /* Likely buggy but no truncation.  Diagnosed by -Wstringop-overflow.  */
     168    CPY (d, a4, strlen (a4) + 1);
     169    CPY (d, S4, strlen (S4) + i);
     170  
     171    CPY (d, a4, strlen (a4));         /* { dg-warning ".strncpy\[^\n\r\]* output truncated before terminating nul copying 3 bytes from a string of the same length" } */
     172    /* As above, buggy but no evidence of truncation.  */
     173    CPY (d, S4, strlen (S4) + 1);
     174  
     175    CPY (d, CHOOSE ("", "1"), 0);     /* { dg-warning ".strncpy\[^\n\r\]* destination unchanged after copying no bytes" } */
     176    CPY (d, CHOOSE ("1", "12"), 0);   /* { dg-warning ".strncpy\[^\n\r\]* destination unchanged after copying no bytes" } */
     177  
     178    CPY (d, CHOOSE ("", "1"), 1);     /* { dg-warning ".strncpy\[^\n\r\]* output may be truncated copying 1 byte from a string of length 1" } */
     179    CPY (d, CHOOSE ("1", ""), 1);     /* { dg-warning ".strncpy\[^\n\r\]* output may be truncated copying 1 byte from a string of length 1" } */
     180    CPY (d, CHOOSE (s, "1"), 1);      /* { dg-warning ".strncpy\[^\n\r\]* output may be truncated copying 1 byte from a string of length 1" } */
     181    CPY (d, CHOOSE (s, t), 1);
     182  
     183    CPY (d, CHOOSE ("", "1"), 2);
     184    CPY (d, CHOOSE ("1", ""), 2);
     185    CPY (d, CHOOSE ("1", "2"), 2);
     186    CPY (d, CHOOSE ("1", s), 2);
     187    CPY (d, CHOOSE (s, "1"), 2);
     188    CPY (d, CHOOSE (s, t), 2);
     189  
     190    CPY (d, CHOOSE ("", "123"), 1);   /* { dg-warning ".strncpy\[^\n\r\]* output may be truncated copying 1 byte from a string of length 3" } */
     191    CPY (d, CHOOSE ("1", "123"), 1);  /* { dg-warning ".strncpy\[^\n\r\]* output truncated before terminating nul copying 1 byte from a string of the same length" } */
     192    CPY (d, CHOOSE ("12", "123"), 1); /* { dg-warning ".strncpy\[^\n\r\]* output truncated copying 1 byte from a string of length 2" } */
     193    CPY (d, CHOOSE ("123", "12"), 1); /* { dg-warning ".strncpy\[^\n\r\]* output truncated copying 1 byte from a string of length 2" } */
     194  
     195    {
     196      signed char n = strlen (s);     /* { dg-message "length computed here" } */
     197      CPY (d, s, n);                  /* { dg-warning ".strncpy\[^\n\r\]* output truncated before terminating nul copying as many bytes from a string as its length" } */
     198    }
     199  
     200    {
     201      short n = strlen (s);           /* { dg-message "length computed here" } */
     202      CPY (d, s, n);                  /* { dg-warning ".strncpy\[^\n\r\]* output truncated before terminating nul copying as many bytes from a string as its length" } */
     203    }
     204  
     205    {
     206      int n = strlen (s);             /* { dg-message "length computed here" } */
     207      CPY (d, s, n);                  /* { dg-warning ".strncpy\[^\n\r\]* output truncated before terminating nul copying as many bytes from a string as its length" } */
     208    }
     209  
     210    {
     211      unsigned n = strlen (s);        /* { dg-message "length computed here" } */
     212      CPY (d, s, n);                  /* { dg-warning ".strncpy\[^\n\r\]* output truncated before terminating nul copying as many bytes from a string as its length" } */
     213    }
     214  
     215    {
     216      size_t n;
     217      n = strlen (s);                 /* { dg-message "length computed here" } */
     218      CPY (d, s, n);                  /* { dg-warning ".strncpy\[^\n\r\]* output truncated before terminating nul copying as many bytes from a string as its length" } */
     219    }
     220  
     221    {
     222      size_t n;
     223      char *dp2 = d + 1;
     224      n = strlen (s);                 /* { dg-message "length computed here" } */
     225      CPY (dp2, s, n);                /* { dg-warning ".strncpy\[^\n\r\]* output truncated before terminating nul copying as many bytes from a string as its length" } */
     226    }
     227  
     228    {
     229      /* The following truncates the terminating nul.  The warning should
     230         say that but doesn't.  */
     231      int n;
     232      n = strlen (s) - 1;
     233      CPY (d, s, n);                  /* { dg-warning "\\\[-Wstringop-truncation" } */
     234    }
     235  
     236    {
     237      /* Same as above.  */
     238      size_t n;
     239      n = strlen (s) - 1;
     240      CPY (d, s, n);                  /* { dg-warning "\\\[-Wstringop-truncation" } */
     241    }
     242  
     243    {
     244      size_t n = strlen (s) - strlen (s);
     245      CPY (d, s, n);                  /* { dg-warning ".strncpy\[^\n\r\]* destination unchanged after copying no bytes" } */
     246    }
     247  
     248    {
     249      /* This use of strncpy is dubious but it's probably not worth
     250         worrying about (truncation may not actually take place when
     251         i is the result).  It is diagnosed with -Wstringop-overflow
     252         (although more by accident than by design).
     253  
     254         size_t n = i < strlen (s) ? i : strlen (s);
     255         CPY (d, s, n);
     256      */
     257    }
     258  }
     259  
     260  
     261  /* Verify strncpy warnings for arrays of known bounds.  */
     262  
     263  void test_strncpy_array (Dest *pd, int i, const char* s)
     264  {
     265  #undef CPY
     266  #define CPY(d, s, len) (strncpy ((d), (s), (len)), sink (d))
     267  
     268    CPY (dst7, s, 7);                 /* { dg-warning "specified bound 7 equals destination size" } */
     269    CPY (dst7, s, sizeof dst7);       /* { dg-warning "specified bound 7 equals destination size" } */
     270  
     271    CPY (dst2_5[0], s, sizeof dst2_5[0]); /* { dg-warning "specified bound 5 equals destination size" "bug 77293" } */
     272    CPY (dst2_5[1], s, sizeof dst2_5[1]); /* { dg-warning "specified bound 5 equals destination size" } */
     273  
     274    /* Verify that copies that nul-terminate are not diagnosed.  */
     275    CPY (dst7,     "",       sizeof dst7);
     276    CPY (dst7 + 6, "",       sizeof dst7 - 6);
     277    CPY (dst7,     "1",      sizeof dst7);
     278    CPY (dst7 + 1, "1",      sizeof dst7 - 1);
     279    CPY (dst7,     "123456", sizeof dst7);
     280    CPY (dst7 + 1, "12345",  sizeof dst7 - 1);
     281  
     282    CPY (dst7 + i, s,        6);
     283    CPY (dst7 + i, s,        7);      /* { dg-warning "specified bound 7 equals destination size" } */
     284    /* The following two calls are diagnosed by -Wstringop-overflow.  */
     285    CPY (dst7 + i, s,        8);
     286    CPY (dst7 + i, s,        UR (8, 9));
     287  
     288    /* No nul-termination here.  */
     289    CPY (dst7 + 2, "12345",  sizeof dst7 - 2);    /* { dg-warning "output truncated before terminating nul copying 5 bytes from a string of the same length" } */
     290  
     291    /* Because strnlen appends as many NULs as necessary to write the specified
     292       number of byts the following doesn't (necessarily) truncate but rather
     293       overflow, and so is diagnosed by -Wstringop-overflow.  */
     294    CPY (dst7, s, 8);
     295  
     296    CPY (dst7 + 1, s, 6);             /* { dg-warning "specified bound 6 equals destination size" } */
     297    CPY (dst7 + 6, s, 1);             /* { dg-warning "specified bound 1 equals destination size" } */
     298  
     299    CPY (pd->a5, s, 5);               /* { dg-warning "specified bound 5 equals destination size" } */
     300    CPY (pd->a5, s, sizeof pd->a5);   /* { dg-warning "specified bound 5 equals destination size" } */
     301  
     302    CPY (pd->a5 + i, s, sizeof pd->a5);   /* { dg-warning "specified bound 5 equals destination size" "member array" } */
     303  
     304    /* Verify that a copy that nul-terminates is not diagnosed.  */
     305    CPY (pd->a5, "1234", sizeof pd->a5);
     306  
     307    /* Same above, diagnosed by -Wstringop-overflow.  */
     308    CPY (pd->a5, s, 6);
     309  
     310    /* Exercise destination with attribute "nonstring".  */
     311    CPY (pd->c3ns, "", 3);
     312    CPY (pd->c3ns, "", 1);
     313    /* It could be argued that truncation in the literal case should be
     314       diagnosed even for non-strings.  Using strncpy in this case is
     315       pointless and should be replaced with memcpy.  But it would likely
     316       be viewed as a false positive.  */
     317    CPY (pd->c3ns, "12", 1);
     318    CPY (pd->c3ns, "12", 2);
     319    CPY (pd->c3ns, "12", 3);
     320    CPY (pd->c3ns, "123", 3);
     321    CPY (pd->c3ns, s, 3);
     322    CPY (pd->c3ns, s, sizeof pd->c3ns);
     323  
     324    /* Verify that the idiom of calling strncpy with a bound equal to
     325       the size of the destination (and thus potentially without NUL-
     326       terminating it) immediately followed by setting the last element
     327       of the array to NUL is not diagnosed.  */
     328    {
     329      /* This might be better written using memcpy() but it's safe so
     330         it shouldn't be diagnosed.  */
     331      strncpy (dst7, "0123456", sizeof dst7);   /* { dg-bogus "\\\[-Wstringop-truncation]" } */
     332      dst7[sizeof dst7 - 1] = '\0';
     333      sink (dst7);
     334    }
     335  
     336    {
     337      const char a[] = "0123456789";
     338      strncpy (dst7, a, sizeof dst7);
     339      dst7[sizeof dst7 - 1] = '\0';
     340      sink (dst7);
     341    }
     342  
     343    {
     344      strncpy (dst7, s, sizeof dst7);
     345      dst7[sizeof dst7 - 1] = '\0';
     346      sink (dst7);
     347    }
     348  
     349    {
     350      strncpy (pd->a5, "01234", sizeof pd->a5);   /* { dg-bogus "\\\[-Wstringop-truncation]" } */
     351      pd->a5[sizeof pd->a5 - 1] = '\0';
     352      sink (pd);
     353    }
     354  
     355    {
     356      strncpy (pd->a5, s, sizeof pd->a5);
     357      pd->a5[sizeof pd->a5 - 1] = '\0';
     358      sink (pd);
     359    }
     360  
     361    {
     362      unsigned n = 7;
     363      char *p = (char*)__builtin_malloc (n);
     364      strncpy (p, s, n);
     365      p[n - 1] = '\0';
     366      sink (p);
     367    }
     368  
     369    {
     370      /* This should be diagnosed because the NUL-termination doesn't
     371         immediately follow the strncpy call (sink may expect pd->a5
     372         to be NUL-terminated).  */
     373      strncpy (pd->a5, s, sizeof pd->a5); /* { dg-warning "specified bound 5 equals destination size" } */
     374      sink (pd);
     375      pd->a5[sizeof pd->a5] = '\0';
     376      sink (pd);
     377    }
     378  }
     379  
     380  typedef struct Flex
     381  {
     382    size_t n;
     383    char a0[0];
     384    char ax[];
     385  } Flex;
     386  
     387  extern char array[];
     388  
     389  /* Verify that no warning is issued for array of unknown bound, flexible
     390     array members, or zero-length arrays, except when the source is definitely
     391     truncated.  */
     392  
     393  void test_strncpy_flexarray (Flex *pf, const char* s)
     394  {
     395  #undef CPY
     396  #define CPY(d, s, len) (strncpy ((d), (s), (len)), sink (d))
     397  
     398    CPY (array, "12345", 7);
     399    CPY (array, "12345", 123);
     400  
     401    CPY (array, s, 7);
     402    CPY (array, s, 123);
     403  
     404    CPY (pf->a0, s, 1);
     405    CPY (pf->a0, s, 1234);
     406  
     407    CPY (pf->a0, "",      1);
     408    CPY (pf->a0, "12345", 5);          /* { dg-warning "output truncated before terminating nul copying 5 bytes from a string of the same length" } */
     409    CPY (pf->a0, "12345", 1234);
     410  
     411    CPY (pf->ax, s, 5);
     412    CPY (pf->ax, s, 12345);
     413  
     414    CPY (pf->ax, "1234", 5);
     415    CPY (pf->ax, "12345", 5);         /* { dg-warning "output truncated before terminating nul copying 5 bytes from a string of the same length" } */
     416    CPY (pf->ax, "12345", 12345);
     417  }
     418  
     419  /* Verify warnings for dynamically allocated objects.  */
     420  
     421  void test_strncpy_alloc (const char* s)
     422  {
     423    size_t n = 7;
     424    char *d = (char *)__builtin_malloc (n);
     425  
     426    CPY (d, s, n);                    /* { dg-warning "specified bound 7 equals destination size" } */
     427  
     428    Dest *pd = (Dest *)__builtin_malloc (sizeof *pd * n);
     429    CPY (pd->a5, s, 5);               /* { dg-warning "specified bound 5 equals destination size" } */
     430    CPY (pd->a5, s, sizeof pd->a5);   /* { dg-warning "specified bound 5 equals destination size" } */
     431  }
     432  
     433  /* Verify warnings for VLAs.  */
     434  
     435  void test_strncpy_vla (unsigned n, const char* s)
     436  {
     437    char vla[n];
     438    CPY (vla, s, 0);                  /* { dg-warning ".strncpy\[^\n\r\]* destination unchanged after copying no bytes" } */
     439  
     440    CPY (vla, s, 1);
     441    CPY (vla, s, 2);
     442    CPY (vla, s, n);
     443  
     444    CPY (vla, "", 0);                 /* { dg-warning ".strncpy\[^\n\r\]* destination unchanged after copying no bytes" } */
     445    CPY (vla, "", 1);
     446    CPY (vla, S4, 3);                 /* { dg-warning ".strncpy\[^\n\r\]* output truncated before terminating nul copying 3 bytes from a string of the same length" } */
     447    CPY (vla, S4, n);
     448  }