1

The following function MsCommand_push does not work as expected when compiled with Apple clang version 15.0.0 (clang-1500.3.9.4). It is supposed to take as input a variable number of pointer to char (pointing to null-terminating strings) and append them to a buffer. It is part of a command line interpreter and its purpose is to fill a buffer with a string that will be used to launch processes.

The function uses the stdarg.h types and macros. It works as expected on Linuces (compiled with gcc), Windows (compiled with gcc), macOS x86-64 (compiled with clang). It has been working as expected for decades under those environments and others such as SunOS and other UNIX flavors.

On macOS arm64, compiled with clang 15, the assignment to str sometimes does not retrieve the first element of the remaining parameters.

/* global variables accessed in the function */
static char *cmd_buffer = NULL;
static int buffer_size = 0;
static int cmd_len = 0;
static int free_len = 0;
static const int init_buffer_size = 1024;

/* typical call: MsCommand_push(1, "cp "); */
void MsCommand_push(unsigned nb, ...)
{
    int new_len = 0;
    const char *str = NULL;
    char *tmp = NULL;
    unsigned int i = 0;

    va_list param;
    va_list param2;
    va_start(param, nb);
    va_copy(param2,param);

    for (i=0; i < nb; i++)
    {
        str = va_arg(param, char *); /* <--- va_arg returns garbage in first iteration! */
        new_len = new_len + strlen(str);
    }

    va_end(param);

    if (free_len < new_len)
    {
        char *tmp = cmd_buffer;
        cmd_buffer = calloc(cmd_len + result, sizeof(char));
        memcpy(cmd_buffer, tmp, cmd_len * sizeof(char));
        buffer_size = cmd_len + result;
        free_len = result;
    }

    ASSERT(cmd_len > 0);
    tmp = cmd_buffer + cmd_len - 1;

    str = NULL;
    for (i=0; i < nb; i++)
    {
        str = va_arg(param2, char *);
        sprintf(tmp, "%s", str);
        tmp = tmp + strlen(str);
    }

    va_end(param2);

    cmd_len = new_len + cmd_len;
    free_len = free_len - new_len;
}

/* function called before MsCommand_push */
void MsCommand_init() {
    ASSERT((cmd_buffer == NULL) && (init_buffer_size > 0));

    cmd_buffer = (char *)malloc(init_buffer_size);
    free_len = init_buffer_size - 1;
    cmd_len = 1;
    cmd_buffer[0] = '\0';
    buffer_size = init_buffer_size;
}

To make the problem more self-contained and easier to reproduce, I changed the code so that the faulty behavior occurs in a much more simple context than in the actual cases.

To do so, I wrote the following main function:

int main(int argc,
         char **argv)
{
    extern int MsFile_dummy1();
    extern void MsCommand_push(unsigned nb, ...);
    extern void MsCommand_init();
    MsCommand_init();
    MsCommand_push(1, "cp ");

    if (MsFile_dummy1() == 1) {
      return 1;
    }
    return 0;
}

The call to MsCommand_push from main works as expected.

However the call through MsFile_dummy1() falls into the anomalous behavior. This function is just a proxy to include the call:

In another file, we have

int MsFile_dummy1() {
    MsCommand_push(1, "cp ");
    return 0;
}

What could be wrong and how could I fix this issue?

Note: I found a 10-year old discussion on this topic and I suppose it is not up-to-date.

13
  • 2
    please provide a minimal reproducible example. Commented Nov 8, 2024 at 17:00
  • 1
    I don't see declarations for free_len, cmd_buffer, buffer_size. You don't have to use va_copy(); you could reuse param with a second va_start(), but that's very unlikely to be your problem. Commented Nov 8, 2024 at 17:16
  • My guess is you have undefined behavior somewhere else in the program, and it's triggering unrelated problems here. It's inconceivable that clang would have problems with basic use of stdarg. Commented Nov 8, 2024 at 17:34
  • Your usage of stdargs looks fine to me. It does seem very unlikely that Clang has a flaw that would bear on that. The issue, then, is probably related to the cmd_buffer, cmd_len, and free_len variables, or to the function parameters passed on some call. For instance, if free_len or nb is too large, or if one of the argument strings is unterminated, then you might have any manner of unexpected behavior -- or not. Commented Nov 8, 2024 at 19:20
  • Does Apples version of clang support sanitizers? Adding -fsanitize=address,undefined to the compilation options might help track down the issue if so. Commented Nov 8, 2024 at 19:26

1 Answer 1

1

The cause for this problem is that the declaration of the variadic function MsCommand_push is not visible when function MsFile_dummy1 is compiled.

The root cause is that the proper header file had not been included... and the compiler flags included -Wno-implicit-function-declaration.

Thank you for all the helpful comments that pushed me to do a deeper investigation of this problem.

Sign up to request clarification or add additional context in comments.

1 Comment

Well done on finding the problem. I couldn't reproduce it, but I was compiling with a command line clang -Wno-nullability-completeness -O3 -g -std=c18 -Wall -Wextra -Werror -Wmissing-prototypes -Wstrict-prototypes -fno-common stdarg43.c -o stdarg43. The -no-nullability-completeness option is a peculiarity on this particular Mac (an M3 MacBook Pro running macOS Sonoma 14.7.1). The rest of the options are more or less what I always use — and the -Werror ensures I don't run anything that doesn't compile cleanly. Make sure you use a similarly stringent set of compiler options.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.