Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Does const mismatch invoke UB when pointer argument is received by va_arg?

I noticed some potential issue with va_arg macro, that is used to receive an unnamed parameter from variadic function. Consider following, simplified example:

#include <stdio.h>
#include <stdarg.h>

void foo(int n, ...)
{
    va_list ap;  
    const char *s;

    va_start(ap, n);
    for (s = va_arg(ap, const char *); *s != '\0'; s++)
        putchar(*s);
    va_end(ap);
}

int main(void)
{
    char str[] = "xyz";
    foo(1, str);
    return 0;
}

The reference for va_arg macro states that (emphasis mine):

If va_arg is called when there are no more arguments in ap, or if the type of the next argument in ap (after promotions) is not compatible with T, the behavior is undefined (...)

My understanding is that const char * and char * types are not compatible. When char array is passed to the foo, in form of char pointer (that is unchanged by default argument promotions), then the expression, that assumes const-qualified pointer:

s = va_arg(ap, const char *)

could invoke "technical" UB. The same situation would arise, when arr is defined as const array, and the argument is received as char *, as well as for other types e.g. int * and const int *.

like image 233
Grzegorz Szpetkowski Avatar asked Feb 21 '17 21:02

Grzegorz Szpetkowski


3 Answers

Section 6.2.7 of the C11 language standard defines type compatibility, section 6.7.6.1 further specifies it for pointer declarators and section 6.7.3 for type qualifiers. The latter (constraint 10) says:

For two qualified types to be compatible, both shall have the identically qualified version of a compatible type; the order of type qualifiers within a list of specifiers or qualifiers does not affect the specified type.

Also, section 6.7.6.1 says:

For two pointer types to be compatible, both shall be identically qualified and both shall be pointers to compatible types.

According to these two, I understand that const char* and char* are not compatible (because const char and char are not compatible). According to the definition of va_arg in section 7.16.1.1 which requires type compatibility (with a couple of exceptions that do not apply here), I believe that indeed you have undefined behavior in this case.

Now, if we stop playing the lawyer, I don't see how it could be harmful to treat a char* as a const char* --- it would only limit the legal operations. Therefore, I believe that the specification of va_arg is too conservative.

like image 175
nickie Avatar answered Sep 23 '22 01:09

nickie


n1570/6.2.5p28 seems to suggest this should be okay in practice:

...Similarly, pointers to qualified or unqualified versions of compatible types shall have the same representation and alignment requirements...

Since char is compatible with itself, a pointer to char will have the same representation as a pointer to a const char. Since a variable argument function essentially assumes a representation of each subsequent parameter, this appears at first well defined.

As for the other way around, if you use a pointer to char so as to modify a const char, that is indeed UB per n1570/6.7.3p6:

If an attempt is made to modify an object defined with a const-qualified type through use of an lvalue with non-const-qualified type, the behavior is undefined.


To continue the analysis from the answer you linked to in the comments, n1570/6.7.6.1p2:

For two pointer types to be compatible, both shall be identically qualified and both shall be pointers to compatible types.

Since both pointer types are themselves non const, they are identically qualified, the only question that remains is if const char is compatible with char for our purposes.

And it appears that they are not... as nickie pointed out. So on the one hand, this could be construed as UB.

like image 32
StoryTeller - Unslander Monica Avatar answered Sep 22 '22 01:09

StoryTeller - Unslander Monica


Yes, they are not compatible and using va_arg with type const char* when the actual type is char*, and vice-versa, is undefined behavior.

A type is only compatible with itself1.

Additionally, the section on qualifiers, const being one, supports this2.


(Quoted from ISO/IEC 9899:201x)

1 (6.2.7 Compatible type and composite type 1)
Two types have compatible type if their types are the same

2 (6.7.3 Type qualifiers 10)
For two qualified types to be compatible, both shall have the identically qualified version of a compatible type; the order of type qualifiers within a list of specifiers or qualifiers does not affect the specified type.

like image 27
2501 Avatar answered Sep 19 '22 01:09

2501