sonarsource sonar software plugin languages for edition coverage community code c gcc static-analysis c89

software - sonarqube plugin c++



Varargs seguros de tipo en C con gcc (3)

El problema con C variadics es que realmente se atornillan después, no están realmente diseñados en el lenguaje. El problema principal es que los parámetros variadic son anónimos, no tienen identificadores ni identificadores. Esto lleva a las macros de VA difíciles de manejar para generar referencias a parámetros sin nombres. También conduce a la necesidad de indicar a esas macros dónde comienza la lista de variables y de qué tipo de parámetros se espera que sean.

Toda esta información realmente debe estar codificada en la sintaxis adecuada en el propio lenguaje.

Por ejemplo, uno podría extender la sintaxis de C existente con parámetros formales después de los puntos suspensivos, como así

void foo ( ... int counter, float arglist );

Por convención, el primer parámetro podría ser para el conteo de argumentos y el segundo para la lista de argumentos. Dentro del cuerpo de la función, la lista podría tratarse sintácticamente como una matriz.

Con tal convención, los parámetros variables ya no serían anónimos. Dentro del cuerpo de la función, el contador puede ser referenciado como cualquier otro parámetro y los elementos de la lista pueden ser referenciados como si fueran elementos de una matriz de un parámetro de una matriz, como tal

void foo ( ... int counter, float arglist ) { unsigned i; for (i=0; i<counter; i++) { printf("list[%i] = %f/n", i, arglist[i]); } }

Con una característica de este tipo incorporada en el propio idioma, cada referencia a arglist[i] se traduciría a las direcciones respectivas en el marco de la pila. No habría necesidad de hacer esto a través de macros.

Además, el compilador insertaría automáticamente el recuento de argumentos, lo que reduciría aún más la posibilidad de error.

Una llamada a

foo(1.23, 4.56, 7.89);

sería compilado como si hubiera sido escrito

foo(3, 1.23, 4.56, 7.89);

Dentro del cuerpo de la función, cualquier acceso a un elemento más allá del número real de argumentos realmente pasados ​​podría verificarse en tiempo de ejecución y causar una falla de tiempo de compilación, lo que mejora considerablemente la seguridad.

Por último, pero no por ello menos importante, todos los parámetros variadic están escritos y pueden ser verificados en el momento de la compilación, al igual que los parámetros no variadic.

En algunos casos de uso, por supuesto, sería deseable tener tipos alternativos, como cuando se escribe una función para almacenar claves y valores en una colección. Esto también podría ser acomodado simplemente permitiendo parámetros más formales después de los puntos suspensivos, como así

void store ( collection dict, ... int counter, key_t key, val_t value );

Esta función podría entonces ser llamada como

store(dict, key1, val1, key2, val2, key3, val3);

pero sería compilado como si hubiera sido escrito

store(dict, 3, key1, val1, key2, val2, key3, val3);

Los tipos de parámetros reales se compilarán con los parámetros formales variadic correspondientes.

Dentro del cuerpo de la función, el contador volvería a ser referenciado por su identificador, las claves y los valores serían referenciados como si fueran matrices,

key[i] refiere a la clave del par de value[i] / clave i-th el value[i] refiere al valor del par de valor i-th

y estas referencias se compilarían en sus respectivas direcciones en el marco de pila.

Nada de esto es realmente difícil de hacer, ni lo ha sido nunca. Sin embargo, la filosofía de diseño de C simplemente no es propicia para tales características.

Sin un implementador de compilación de C (o un implementador de preprocesador de C) que tome la iniciativa de implementar este o un esquema similar, es poco probable que veamos algo de este tipo en C.

El problema es que la gente que está interesada en la seguridad de tipos y que está dispuesta a trabajar para construir sus propios compiladores suele llegar a la conclusión de que el lenguaje C está más allá del salvamento y que uno puede comenzar nuevamente con un lenguaje mejor diseñado para empezar. .

Yo mismo estuve allí, finalmente decidí abandonar el intento, luego implementar uno de los lenguajes de Wirth y agregarle variadics de tipo seguro. Desde entonces me he encontrado con otras personas que me contaron sobre sus propios intentos fallidos. Los variados seguros de tipo apropiado en C parecen estar preparados para seguir siendo esquivos.

Muchas veces quiero que una función reciba un número variable de argumentos, terminados por NULL, por ejemplo

#define push(stack_t stack, ...) _push(__VARARG__, NULL); func _push(stack_t stack, char *s, ...) { va_list args; va_start(args, s); while (s = va_arg(args, char*)) push_single(stack, s); }

¿Puedo indicar a gcc o clang que avise si foo recibe variables que no son char* ? Algo similar a __attribute__(format) , pero para múltiples argumentos del mismo tipo de puntero.


Sé que estás pensando en usar __attribute__((sentinel)) alguna manera, pero esto es una __attribute__((sentinel)) falsa.

Lo que quieres es hacer algo como esto:

#define push(s, args...) ({ / char *_args[] = {args}; / _push(s,_args,sizeof(_args)/sizeof(char*)); / })

que envuelve:

void _push(stack_t s, char *args[], int argn);

que puedes escribir exactamente de la manera que esperas que puedas escribirlo!

Entonces puedes llamar:

push(stack, "foo", "bar", "baz"); push(stack, "quux");


Solo puedo pensar en algo como esto:

#include <stddef.h> #include <stdio.h> #include <stdlib.h> typedef struct tArg { const char* Str; struct tArg* Next; } tArg; tArg* Arg(const char* str, tArg* nextArg) { tArg* p = malloc(sizeof(tArg)); if (p != NULL) { p->Str = str; p->Next = nextArg; } else { while (nextArg != NULL) { p = nextArg->Next; free(nextArg); nextArg = p; } } return p; } void PrintR(tArg* arg) { while (arg != NULL) { tArg* p; printf("%s", arg->Str); p = arg->Next; free(arg); arg = p; } } void (*(*(*(*(*(*(*Print8 (const char* Str)) (const char*)) (const char*)) (const char*)) (const char*)) (const char*)) (const char*)) (const char*) { printf("%s", Str); // There''s probably a UB here: return (void(*(*(*(*(*(*(*) (const char*)) (const char*)) (const char*)) (const char*)) (const char*)) (const char*)) (const char*))&Print8; } int main(void) { PrintR(Arg("HELLO", Arg(" ", Arg("WORLD", Arg("!", Arg("/n", NULL)))))); // PrintR(Arg(1, NULL)); // warning/error // PrintR(Arg(&main, NULL)); // warning/error // PrintR(Arg(0, NULL)); // no warning/error // PrintR(Arg((void*)1, NULL)); // no warning/error Print8("hello")(" ")("world")("!")("/n"); // Same warning/error compilation behavior as with PrintR() return 0; }