2

I get this warning with gcc -std=gnu17 -Wall -Werror -Wshadow -O3 test.c:

In function ‘insertString’,
    inlined from ‘replaceString’ at test.c:94:5,
    inlined from ‘main’ at test.c:110:22:
test.c:69:5: error: ‘strncat’ output may be truncated copying between 0 and 77 bytes from a string of length 80 [-Werror=stringop-truncation]
     strncat(source, buffer, STRING_SIZE - 1 - position - strlen(stringToInsert));
     ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
cc1: all warnings being treated as errors

Removing the do while loop (without any changes to the strncat() statement in question) from main() makes the warning go away.

  1. What does the warning mean and why does it go away?
  2. What changes should I incorporate in the code so that the above gcc command doesn't trigger the warning? The solution cannot simply disable the warning (with fe. #pragma statements). The solution has to use strncat() function.

Not important: This is for learning purposes, please be descriptive. The program solves an exercise 9 from chapter 9 of the book "Programming in C (4th Edition)" by Stephen G. Kochan.

The code:

#include <stdio.h>
#include <string.h>
#include <stdbool.h>

#define STRING_SIZE 81

int findString(const char strToSearch[], const char strSought[])
{
    int strToSearchLength = strlen(strToSearch);
    int strSoughtLength = strlen(strSought);

    for (int i = 0; i <= strToSearchLength - 1; ++i)
    {
        if (strToSearch[i] == strSought[0])
        {
            int j = 0;
            while (strToSearch[i+j] == strSought[j])
            {
                if (strSought[j+1] == '\0')
                {
                    return i;
                }
                ++j;
            }
        }
        else if (i > strToSearchLength - strSoughtLength - 1)
        {
            return -1;
        }
    }

    return -1;
}

bool removeString(char source[], const int start, const int nCharsToRemove)
{
    int i, sourceLength = strlen(source);

    if (start + nCharsToRemove > sourceLength || start < 0 || nCharsToRemove < 0)
    {
        printf("Error in function removeString(): invalid parameters.\n");
        return false;
    }
    else
    {
        for (i = start; i < sourceLength; ++i)
        {
            source[i] = source[i + nCharsToRemove];
        }
        source[i] = '\0';
        return true;
    }
}

void insertString(char source[], const char stringToInsert[], const int position)
{
    char buffer[STRING_SIZE];

    int i = 0;
    while (source[position + i] != '\0' && position + i < STRING_SIZE - 1)
    {
        buffer[i] = source[position + i];
        ++i;
    }
    buffer[i] = '\0';

    source[position] = '\0';
    strncat(source, stringToInsert, STRING_SIZE - 1 - position);
// THE STATEMENT MENTIONED IN THE WARNING:
    strncat(source, buffer, STRING_SIZE - 1 - position - strlen(stringToInsert));
}

/* A function to replace the first occurence of the string s1
 * inside the source string, if it exists, with the string s2
 */
bool replaceString(char source[], const char s1[], const char s2[])
{
    int findString(const char strToSearch[], const char strSought[]);
    bool removeString(char source[], const int start, const int nCharsToRemove);
    void insertString(char source[], const char stringToInsert[], const int position);
    int s1_position;
    bool success;

    // locate s1 inside source
    s1_position = findString(source, s1);
    if (s1_position == -1)
        return false;

    // remove s1 from source
    success = removeString(source, s1_position, strlen(s1));
    if (! success)
        return false;

    // insert s2 into source at the proper location
    insertString(source, s2, s1_position);

    return true;
}

int main(void)
{
    char text[STRING_SIZE] = "1 is first*";

// uncommenting the following comment and discarding what follows it makes the warning go away
/*
    replaceString(text, "is", "one");
    printf("%s\n", text);
*/
    bool stillFound;
    do
        stillFound = replaceString(text, "is", "one");
    while (stillFound);
    printf("%s\n", text);

    return 0;
}
robertmoar
  • 21
  • 1
  • 4
  • If the buffer is known to be too small *at compile time* then use a larger buffer. – Weather Vane Dec 03 '20 at 14:00
  • `The solution has to use strncat() function.` ? Why? What about `strlcat`? Did you copy what is inside your book? `The code:` This is an example, but surely it's not _minimal_. [MCVE] could be way shorter, no?. – KamilCuk Dec 03 '20 at 14:05
  • Would this answer your question? https://stackoverflow.com/questions/50198319/gcc-8-wstringop-truncation-what-is-the-good-practice – KamilCuk Dec 03 '20 at 14:29
  • @KamilCuk The link you posted doesn't explain what the error means, what triggers it, why removing the `do while` loop makes it go away and how to fix the problem, if there's any. – robertmoar Dec 03 '20 at 16:17
  • @KamilCuk Using another function bypasses the problem. Warning is there for a reason, I think it's good to actually understand what's happening. This is my code. I can see how I could shorten it by replacing a few functions with constants, but I don't see how I could make the problem easier to see, do you have any suggestions in case you disagree? – robertmoar Dec 03 '20 at 16:17
  • @WeatherVane Are you sure the buffer is too small? The program executes properly without the `do while` loop despite the fact that it doesn't affect the result. – robertmoar Dec 03 '20 at 16:17
  • @WeatherVane Destination buffer, ie. s1? From C17 standard: char * strncat(char * restrict s1, const char * restrict s2, size_t n); The strncat function appends not more than n characters (a null character and characters that follow it are not appended) from the array pointed to by s2 to the end of the string pointed to by s1. – robertmoar Dec 03 '20 at 16:36
  • Yes you are right. – Weather Vane Dec 03 '20 at 16:38

1 Answers1

0

What does the warning mean

That is possible, potentially, that buffer will point to a string with 80 characters, but the length STRING_SIZE - 1 - position - strlen(stringToInsert) will be lower then 80. The warning was created to detect cases where not the whole source buffer would be copied to destination (ie. in a strcat(destination, source) call). It may potentially happen. In such case also the destination buffer will not be zero terminated.

why does it go away?

Different code makes compiler make different decisions. In this case compiler doesn't inline the calls, which most probably affects some static analysis done by the compiler. Adding static or attribute((always_inline)) to functions restores the warning. It's hard to answer "why" exactly - either the answer is too broad or too detailed. I believe inspect gcc sources or RTL output to know more.

What changes should I incorporate in the code so that the above gcc command doesn't trigger the warning?

Do not use strncat. Use memcpy. I think something along:

char *dest = &source[position]; // where we copy to
size_t destfree = STRING_SIZE - 1 - position; // how much space we have there
// helper macro
#define MIN(a, b)  ((a)<(b)?(a):(b))

size_t to_copy = MIN(strlen(stringToInsert), destfree);
memcpy(dest, stringToInsert, to_copy);
dest += to_copy;
destfree -= to_copy;

to_copy = MIN(strlen(buffer), destfree);
memcpy(dest, buffer, to_copy);
dest += to_copy;
destfree -= to_copy;

dest[0] = '\0';
KamilCuk
  • 120,984
  • 8
  • 59
  • 111