From ed30a473761d1e1125532420686392d07d8c43f8 Mon Sep 17 00:00:00 2001 From: Andre Maroneze <andre.maroneze@cea.fr> Date: Tue, 26 Mar 2024 18:34:16 +0100 Subject: [PATCH] [Libc] improve several specifications (mainly string.h/stdlib.h) and add tests --- share/libc/__fc_string_axiomatic.h | 7 +- share/libc/glob.h | 1 + share/libc/stdlib.c | 12 +- share/libc/stdlib.h | 211 +++++++++----- share/libc/string.h | 445 +++++++++++++++++------------ share/libc/wchar.h | 2 +- tests/libc/stdlib_c_env.c | 4 + tests/libc/stdlib_h.c | 23 ++ tests/libc/string_h.c | 87 +++++- 9 files changed, 516 insertions(+), 276 deletions(-) diff --git a/share/libc/__fc_string_axiomatic.h b/share/libc/__fc_string_axiomatic.h index c1b90dce4bc..d137cba4eeb 100644 --- a/share/libc/__fc_string_axiomatic.h +++ b/share/libc/__fc_string_axiomatic.h @@ -270,10 +270,6 @@ __BEGIN_DECLS @ } @*/ -/*@ logic ℤ minimum(ℤ i, ℤ j) = i < j ? i : j; - @ logic ℤ maximum(ℤ i, ℤ j) = i < j ? j : i; - @*/ - /*@ predicate valid_string{L}(char *s) = @ 0 <= strlen(s) && \valid(s+(0..strlen(s))); @ @@ -299,6 +295,9 @@ __BEGIN_DECLS @ @ predicate valid_wstring_or_null{L}(wchar_t *s) = @ s == \null || valid_wstring(s); + @ + @ logic ℤ strnlen(char *s, ℤ n) = + @ \min(strlen(s), n); @*/ __END_DECLS diff --git a/share/libc/glob.h b/share/libc/glob.h index 4046f1efb02..44a09d7cf70 100644 --- a/share/libc/glob.h +++ b/share/libc/glob.h @@ -36,6 +36,7 @@ __PUSH_FC_STDLIB #define GLOB_APPEND (1 << 5)/* Append to results of a previous call. */ #define GLOB_NOESCAPE (1 << 6)/* Backslashes don't quote metacharacters. */ #define GLOB_PERIOD (1 << 7)/* Leading `.' can be matched by metachars. */ +#define GLOB_MAGCHAR (1 << 8)/* GNU-specific */ #define GLOB_NOSPACE 1 /* Ran out of memory. */ #define GLOB_ABORTED 2 /* Read error. */ diff --git a/share/libc/stdlib.c b/share/libc/stdlib.c index ca6863450b7..c3fd0cfcbd0 100644 --- a/share/libc/stdlib.c +++ b/share/libc/stdlib.c @@ -128,7 +128,7 @@ int putenv(char *string) // 2. key in string found in env ==> modify an existing entry if (Frama_C_nondet(0, 1)) { if (Frama_C_nondet(0, 1)) { - //TODO: errno = ENOMEM; + errno = ENOMEM; return Frama_C_interval(INT_MIN, INT_MAX); // return a non-zero value } __fc_env[Frama_C_interval(0, ARG_MAX-1)] = string; @@ -139,12 +139,12 @@ int putenv(char *string) int setenv(const char *name, const char *value, int overwrite) { if (strchr(name, '=')) { - //TODO: errno = EINVAL; + errno = EINVAL; return -1; } size_t namelen = strlen(name); if (namelen == 0) { - //TODO: errno = EINVAL; + errno = EINVAL; return -1; } @@ -155,7 +155,7 @@ int setenv(const char *name, const char *value, int overwrite) // 2. found 'name' but will not overwrite // 3. did not find name and has available memory if (Frama_C_nondet(0, 1)) { - //TODO: errno = ENOMEM; + errno = ENOMEM; return -1; } else { if (Frama_C_nondet(0, 1)) { @@ -169,12 +169,12 @@ int setenv(const char *name, const char *value, int overwrite) int unsetenv(const char *name) { if (strchr(name, '=')) { - //TODO: errno = EINVAL; + errno = EINVAL; return -1; } size_t namelen = strlen(name); if (namelen == 0) { - //TODO: errno = EINVAL; + errno = EINVAL; return -1; } diff --git a/share/libc/stdlib.h b/share/libc/stdlib.h index 4ac3ce4bd90..9042a4ca36c 100644 --- a/share/libc/stdlib.h +++ b/share/libc/stdlib.h @@ -70,26 +70,26 @@ typedef struct __fc_lldiv_t { #define MB_CUR_MAX __FC_MB_CUR_MAX /*@ - requires valid_nptr: \valid_read(nptr); // cannot be precise, valid_read_string too strong - assigns \result \from indirect:nptr, indirect:nptr[0 ..]; + requires valid_nptr: valid_read_string(nptr); + assigns \result \from indirect:nptr, indirect:nptr[0 .. strlen(nptr)]; */ extern double atof(const char *nptr); /*@ - requires valid_nptr: \valid_read(nptr); // cannot be precise, valid_read_string too strong - assigns \result \from indirect:nptr, indirect:nptr[0 ..]; + requires valid_nptr: valid_read_string(nptr); + assigns \result \from indirect:nptr, indirect:nptr[0 .. strlen(nptr)]; */ extern int atoi(const char *nptr); /*@ - requires valid_nptr: \valid_read(nptr); // cannot be precise, valid_read_string too strong - assigns \result \from indirect:nptr, indirect:nptr[0 ..]; + requires valid_nptr: valid_read_string(nptr); + assigns \result \from indirect:nptr, indirect:nptr[0 .. strlen(nptr)]; */ extern long int atol(const char *nptr); /*@ - requires valid_nptr: \valid_read(nptr); // cannot be precise, valid_read_string too strong - assigns \result \from indirect:nptr, indirect:nptr[0 ..]; + requires valid_nptr: valid_read_string(nptr); + assigns \result \from indirect:nptr, indirect:nptr[0 .. strlen(nptr)]; */ extern long long int atoll(const char *nptr); @@ -98,19 +98,21 @@ extern long long int atoll(const char *nptr); /*@ requires valid_string_nptr: valid_read_string(nptr); requires separation: \separated(nptr, endptr); - assigns \result \from indirect:nptr, indirect:nptr[0 ..]; - assigns *endptr \from nptr, indirect:nptr[0 ..], indirect:endptr; + assigns \result \from indirect:nptr, indirect:nptr[0 .. strlen(nptr)]; + assigns *endptr \from nptr, indirect:nptr[0 .. strlen(nptr)], + indirect:endptr; behavior no_storage: assumes null_endptr: endptr == \null; - assigns \result \from indirect:nptr, indirect:nptr[0 ..]; + assigns \result \from indirect:nptr, indirect:nptr[0 .. strlen(nptr)]; behavior store_position: assumes nonnull_endptr: endptr != \null; requires valid_endptr: \valid(endptr); - assigns \result \from indirect:nptr, indirect:nptr[0 ..]; - assigns *endptr \from nptr, indirect:nptr[0 ..], indirect:endptr; + assigns \result \from indirect:nptr, indirect:nptr[0 .. strlen(nptr)]; + assigns *endptr \from nptr, indirect:nptr[0 .. strlen(nptr)], + indirect:endptr; ensures initialization: \initialized(endptr); ensures valid_endptr_content: \valid_read(*endptr); - ensures position_subset: \subset(*endptr, nptr + (0 ..)); + ensures endptr_same_base: \base_addr(*endptr) == \base_addr(nptr); complete behaviors; disjoint behaviors; */ @@ -120,19 +122,20 @@ extern double strtod(const char * restrict nptr, /*@ requires valid_string_nptr: valid_read_string(nptr); requires separation: \separated(nptr, endptr); - assigns \result \from indirect:nptr, indirect:nptr[0 ..]; - assigns *endptr \from nptr, indirect:nptr[0 ..], indirect:endptr; + assigns \result \from indirect:nptr, indirect:nptr[0 .. strlen(nptr)]; + assigns *endptr \from nptr, indirect:nptr[0 .. strlen(nptr)], + indirect:endptr; behavior no_storage: assumes null_endptr: endptr == \null; - assigns \result \from indirect:nptr, indirect:nptr[0 ..]; + assigns \result \from indirect:nptr, indirect:nptr[0 .. strlen(nptr)]; behavior store_position: assumes nonnull_endptr: endptr != \null; requires valid_endptr: \valid(endptr); - assigns \result \from indirect:nptr, indirect:nptr[0 ..]; + assigns \result \from indirect:nptr, indirect:nptr[0 .. strlen(nptr)]; assigns *endptr \from nptr, indirect:nptr[0 ..], indirect:endptr; ensures initialization: \initialized(endptr); ensures valid_endptr_content: \valid_read(*endptr); - ensures position_subset: \subset(*endptr, nptr + (0 ..)); + ensures endptr_same_base: \base_addr(*endptr) == \base_addr(nptr); complete behaviors; disjoint behaviors; */ @@ -142,19 +145,21 @@ extern float strtof(const char * restrict nptr, /*@ requires valid_string_nptr: valid_read_string(nptr); requires separation: \separated(nptr, endptr); - assigns \result \from indirect:nptr, indirect:nptr[0 ..]; - assigns *endptr \from nptr, indirect:nptr[0 ..], indirect:endptr; + assigns \result \from indirect:nptr, indirect:nptr[0 .. strlen(nptr)]; + assigns *endptr \from nptr, indirect:nptr[0 .. strlen(nptr)], + indirect:endptr; behavior no_storage: assumes null_endptr: endptr == \null; - assigns \result \from indirect:nptr, indirect:nptr[0 ..]; + assigns \result \from indirect:nptr, indirect:nptr[0 .. strlen(nptr)]; behavior store_position: assumes nonnull_endptr: endptr != \null; requires valid_endptr: \valid(endptr); - assigns \result \from indirect:nptr, indirect:nptr[0 ..]; - assigns *endptr \from nptr, indirect:nptr[0 ..], indirect:endptr; + assigns \result \from indirect:nptr, indirect:nptr[0 .. strlen(nptr)]; + assigns *endptr \from nptr, indirect:nptr[0 .. strlen(nptr)], + indirect:endptr; ensures initialization: \initialized(endptr); ensures valid_endptr_content: \valid_read(*endptr); - ensures position_subset: \subset(*endptr, nptr + (0 ..)); + ensures endptr_same_base: \base_addr(*endptr) == \base_addr(nptr); complete behaviors; disjoint behaviors; */ @@ -166,19 +171,24 @@ extern long double strtold(const char * restrict nptr, requires valid_string_nptr: valid_read_string(nptr); requires separation: \separated(nptr, endptr); requires base_range: base == 0 || 2 <= base <= 36; - assigns \result \from indirect:nptr, indirect:nptr[0 ..], indirect:base; - assigns *endptr \from nptr, indirect:nptr[0 ..], indirect:endptr, indirect:base; + assigns \result \from indirect:nptr, indirect:nptr[0 .. strlen(nptr)], + indirect:base; + assigns *endptr \from nptr, indirect:nptr[0 .. strlen(nptr)], + indirect:endptr, indirect:base; behavior no_storage: assumes null_endptr: endptr == \null; - assigns \result \from indirect:nptr, indirect:nptr[0 ..], indirect:base; + assigns \result \from indirect:nptr, indirect:nptr[0 .. strlen(nptr)], + indirect:base; behavior store_position: assumes nonnull_endptr: endptr != \null; requires valid_endptr: \valid(endptr); - assigns \result \from indirect:nptr, indirect:nptr[0 ..], indirect:base; - assigns *endptr \from nptr, indirect:nptr[0 ..], indirect:endptr, indirect:base; + assigns \result \from indirect:nptr, indirect:nptr[0 .. strlen(nptr)], + indirect:base; + assigns *endptr \from nptr, indirect:nptr[0 .. strlen(nptr)], + indirect:endptr, indirect:base; ensures initialization: \initialized(endptr); ensures valid_endptr_content: \valid_read(*endptr); - ensures position_subset: \subset(*endptr, nptr + (0 ..)); + ensures endptr_same_base: \base_addr(*endptr) == \base_addr(nptr); complete behaviors; disjoint behaviors; */ @@ -191,19 +201,24 @@ extern long int strtol( requires valid_string_nptr: valid_read_string(nptr); requires separation: \separated(nptr, endptr); requires base_range: base == 0 || 2 <= base <= 36; - assigns \result \from indirect:nptr, indirect:nptr[0 ..], indirect:base; - assigns *endptr \from nptr, indirect:nptr[0 ..], indirect:endptr, indirect:base; + assigns \result \from indirect:nptr, indirect:nptr[0 .. strlen(nptr)], + indirect:base; + assigns *endptr \from nptr, indirect:nptr[0 .. strlen(nptr)], + indirect:endptr, indirect:base; behavior no_storage: assumes null_endptr: endptr == \null; - assigns \result \from indirect:nptr, indirect:nptr[0 ..], indirect:base; + assigns \result \from indirect:nptr, indirect:nptr[0 .. strlen(nptr)], + indirect:base; behavior store_position: assumes nonnull_endptr: endptr != \null; requires valid_endptr: \valid(endptr); - assigns \result \from indirect:nptr, indirect:nptr[0 ..], indirect:base; - assigns *endptr \from nptr, indirect:nptr[0 ..], indirect:endptr, indirect:base; + assigns \result \from indirect:nptr, indirect:nptr[0 .. strlen(nptr)], + indirect:base; + assigns *endptr \from nptr, indirect:nptr[0 .. strlen(nptr)], + indirect:endptr, indirect:base; ensures initialization: \initialized(endptr); ensures valid_endptr_content: \valid_read(*endptr); - ensures position_subset: \subset(*endptr, nptr + (0 ..)); + ensures endptr_same_base: \base_addr(*endptr) == \base_addr(nptr); complete behaviors; disjoint behaviors; */ @@ -216,19 +231,24 @@ extern long long int strtoll( requires valid_string_nptr: valid_read_string(nptr); requires separation: \separated(nptr, endptr); requires base_range: base == 0 || 2 <= base <= 36; - assigns \result \from indirect:nptr, indirect:nptr[0 ..], indirect:base; - assigns *endptr \from nptr, indirect:nptr[0 ..], indirect:endptr, indirect:base; + assigns \result \from indirect:nptr, indirect:nptr[0 .. strlen(nptr)], + indirect:base; + assigns *endptr \from nptr, indirect:nptr[0 .. strlen(nptr)], + indirect:endptr, indirect:base; behavior no_storage: assumes null_endptr: endptr == \null; - assigns \result \from indirect:nptr, indirect:nptr[0 ..], indirect:base; + assigns \result \from indirect:nptr, indirect:nptr[0 .. strlen(nptr)], + indirect:base; behavior store_position: assumes nonnull_endptr: endptr != \null; requires valid_endptr: \valid(endptr); - assigns \result \from indirect:nptr, indirect:nptr[0 ..], indirect:base; - assigns *endptr \from nptr, indirect:nptr[0 ..], indirect:endptr, indirect:base; + assigns \result \from indirect:nptr, indirect:nptr[0 .. strlen(nptr)], + indirect:base; + assigns *endptr \from nptr, indirect:nptr[0 .. strlen(nptr)], + indirect:endptr, indirect:base; ensures initialization: \initialized(endptr); ensures valid_endptr_content: \valid_read(*endptr); - ensures position_subset: \subset(*endptr, nptr + (0 ..)); + ensures endptr_same_base: \base_addr(*endptr) == \base_addr(nptr); complete behaviors; disjoint behaviors; */ @@ -241,19 +261,24 @@ extern unsigned long int strtoul( requires valid_string_nptr: valid_read_string(nptr); requires separation: \separated(nptr, endptr); requires base_range: base == 0 || 2 <= base <= 36; - assigns \result \from indirect:nptr, indirect:nptr[0 ..], indirect:base; - assigns *endptr \from nptr, indirect:nptr[0 ..], indirect:endptr, indirect:base; + assigns \result \from indirect:nptr, indirect:nptr[0 .. strlen(nptr)], + indirect:base; + assigns *endptr \from nptr, indirect:nptr[0 .. strlen(nptr)], + indirect:endptr, indirect:base; behavior no_storage: assumes null_endptr: endptr == \null; - assigns \result \from indirect:nptr, indirect:nptr[0 ..], indirect:base; + assigns \result \from indirect:nptr, indirect:nptr[0 .. strlen(nptr)], + indirect:base; behavior store_position: assumes nonnull_endptr: endptr != \null; requires valid_endptr: \valid(endptr); - assigns \result \from indirect:nptr, indirect:nptr[0 ..], indirect:base; - assigns *endptr \from nptr, indirect:nptr[0 ..], indirect:endptr, indirect:base; + assigns \result \from indirect:nptr, indirect:nptr[0 .. strlen(nptr)], + indirect:base; + assigns *endptr \from nptr, indirect:nptr[0 .. strlen(nptr)], + indirect:endptr, indirect:base; ensures initialization: \initialized(endptr); ensures valid_endptr_content: \valid_read(*endptr); - ensures position_subset: \subset(*endptr, nptr + (0 ..)); + ensures endptr_same_base: \base_addr(*endptr) == \base_addr(nptr); complete behaviors; disjoint behaviors; */ @@ -266,9 +291,10 @@ extern unsigned long long int strtoull( const unsigned long __fc_rand_max = __FC_RAND_MAX; /* ISO C: 7.20.2 */ -/*@ assigns \result \from __fc_random_counter ; - @ assigns __fc_random_counter \from __fc_random_counter ; - @ ensures result_range: 0 <= \result <= __fc_rand_max ; +/*@ + assigns \result \from indirect:__fc_random_counter; + assigns __fc_random_counter \from __fc_random_counter; + ensures result_range: 0 <= \result <= __fc_rand_max; */ extern int rand(void); @@ -276,7 +302,7 @@ extern int rand(void); extern void srand(unsigned int seed); /*@ - assigns \result \from __fc_random_counter; + assigns \result \from indirect:__fc_random_counter; ensures result_range: 0 <= \result <= __fc_rand_max; */ extern long int random(void); @@ -298,8 +324,9 @@ unsigned short *__fc_p_random48_counter = __fc_random48_counter; extern void srand48 (long int seed); /*@ + requires valid_seed16v: \valid(seed16v+(0..2)); requires initialization:initialized_seed16v: \initialized(seed16v+(0..2)); - assigns __fc_random48_counter[0..2] \from indirect:seed16v[0..2]; + assigns __fc_random48_counter[0..2] \from seed16v[0..2]; assigns __fc_random48_init \from \nothing; assigns \result \from __fc_p_random48_counter; ensures random48_initialized: __fc_random48_init == 1; @@ -308,7 +335,9 @@ extern void srand48 (long int seed); extern unsigned short *seed48(unsigned short seed16v[3]); /*@ - assigns __fc_random48_counter[0..2] \from param[0..5]; + requires valid_param: \valid(param+(0..6)); + requires initialization:initialized_param: \initialized(param+(0..6)); + assigns __fc_random48_counter[0..2] \from param[0..6]; assigns __fc_random48_init \from \nothing; ensures random48_initialized: __fc_random48_init == 1; */ @@ -323,6 +352,7 @@ extern void lcong48(unsigned short param[7]); extern double drand48(void); /*@ + requires valid_xsubi: \valid(xsubi+(0..2)); requires initialization:initialized_xsubi: \initialized(xsubi+(0..2)); assigns __fc_random48_counter[0..2] \from __fc_random48_counter[0..2]; assigns \result \from __fc_random48_counter[0..2]; @@ -339,6 +369,7 @@ extern double erand48(unsigned short xsubi[3]); extern long int lrand48 (void); /*@ + requires valid_xsubi: \valid(xsubi+(0..2)); requires initialization:initialized_xsubi: \initialized(xsubi+(0..2)); assigns __fc_random48_counter[0..2] \from __fc_random48_counter[0..2]; assigns \result \from __fc_random48_counter[0..2]; @@ -355,6 +386,7 @@ extern long int nrand48 (unsigned short xsubi[3]); extern long int mrand48 (void); /*@ + requires valid_xsubi: \valid(xsubi+(0..2)); requires initialization:initialized_xsubi: \initialized(xsubi+(0..2)); assigns __fc_random48_counter[0..2] \from __fc_random48_counter[0..2]; assigns \result \from __fc_random48_counter[0..2]; @@ -520,8 +552,10 @@ extern char *__fc_env[ARG_MAX]; /*@ requires valid_name: valid_read_string(name); - assigns \result \from __fc_env[0..], indirect:name, name[0 ..]; - ensures null_or_valid_result: \result == \null || \valid(\result); + assigns \result \from __fc_env[0..], indirect:name, + indirect:name[0 .. strlen(name)]; + ensures null_or_valid_result: + \result == \null || (\valid(\result) && valid_read_string(\result)); */ extern char *getenv(const char *name); @@ -535,17 +569,51 @@ extern int putenv(char *string); /*@ requires valid_name: valid_read_string(name); requires valid_value: valid_read_string(value); + allocates __fc_env[0..]; assigns \result, __fc_env[0..] - \from __fc_env[0..], indirect:name, indirect:name[0 ..], - indirect:value, indirect:value[0 ..], indirect:overwrite; + \from __fc_env[0..], indirect:name[0 .. strlen(name)], + indirect:value[0 .. strlen(value)], indirect:overwrite; + assigns __fc_env[0..][0..] \from name[0 .. strlen(name)], + value[0 .. strlen(value)], + indirect:__fc_env[0..], + indirect:overwrite; ensures result_ok_or_error: \result == 0 || \result == -1; + behavior invalid_name: + assumes name_empty_or_with_equals_sign: + strlen(name) == 0 || strchr(name, '='); + allocates \nothing; + assigns \result \from indirect:name[0 .. strlen(name)]; + assigns __fc_errno \from indirect:name[0 .. strlen(name)]; + ensures error: \result == -1; + ensures errno_set: __fc_errno == EINVAL; + behavior out_of_memory: + assumes not_enough_memory: !is_allocable(strlen(name) + strlen(value) + 2); + allocates \nothing; + assigns \result, __fc_errno \from indirect:name[0 .. strlen(name)], + indirect:value[0 .. strlen(value)], + indirect:overwrite; + ensures error: \result == -1; + ensures errno_set: __fc_errno == ENOMEM; + behavior ok: + assumes name_not_empty: strlen(name) > 0; + assumes name_has_no_equals_sign: !strchr(name, '='); + assumes enough_memory: is_allocable(strlen(name) + strlen(value) + 2); + allocates __fc_env[0..]; + assigns \result, __fc_env[0..] + \from __fc_env[0..], indirect:name[0 .. strlen(name)], + indirect:value[0 .. strlen(value)], indirect:overwrite; + assigns __fc_env[0..][0..] \from name[0 .. strlen(name)], + value[0 .. strlen(value)], + indirect:__fc_env[0..], + indirect:overwrite; + ensures ok: \result == 0; */ extern int setenv(const char *name, const char *value, int overwrite); /*@ requires valid_name: valid_read_string(name); assigns \result, __fc_env[0..] - \from __fc_env[0..], indirect:name, indirect:name[0 ..]; + \from __fc_env[0..], indirect:name, indirect:name[0 .. strlen(name)]; ensures result_ok_or_error: \result == 0 || \result == -1; */ extern int unsetenv(const char *name); @@ -559,7 +627,8 @@ extern void quick_exit(int status) __attribute__ ((__noreturn__)); /*@ requires null_or_valid_string_command: command == \null || valid_read_string(command); - assigns \result \from indirect:command, indirect:command[0 ..]; + assigns \result \from indirect:command, + indirect:command[0 .. strlen(command)]; */ extern int system(const char *command); @@ -638,11 +707,25 @@ extern long int labs(long int j); */ extern long long int llabs(long long int j); -/*@ assigns \result \from numer,denom ; */ +/*@ + requires denom_nonzero: denom != 0; + requires no_overflow: !(numer == INT_MIN && denom == -1); + assigns \result \from numer, denom; +*/ extern div_t div(int numer, int denom); -/*@ assigns \result \from numer,denom ; */ + +/*@ + requires denom_nonzero: denom != 0; + requires no_overflow: !(numer == LONG_MIN && denom == -1); + assigns \result \from numer, denom; +*/ extern ldiv_t ldiv(long int numer, long int denom); -/*@ assigns \result \from numer,denom ; */ + +/*@ + requires denom_nonzero: denom != 0; + requires no_overflow: !(numer == LLONG_MIN && denom == -1); + assigns \result \from numer, denom; +*/ extern lldiv_t lldiv(long long int numer, long long int denom); /* ISO C: 7.20.7 */ diff --git a/share/libc/string.h b/share/libc/string.h index 9ef1f1620be..e6af50f03f3 100644 --- a/share/libc/string.h +++ b/share/libc/string.h @@ -76,6 +76,7 @@ extern int memcmp (const void *s1, const void *s2, size_t n); @ assigns \result \from s, c, ((unsigned char*)s)[0..n-1]; @ behavior found: @ assumes char_found: memchr((char*)s,c,n); + @ ensures result_valid_read: \valid_read((char*)\result); @ ensures result_same_base: \base_addr(\result) == \base_addr(s); @ ensures result_char: *(char*)\result == c; @ ensures result_in_str: \forall integer i; @@ -88,9 +89,7 @@ extern int memcmp (const void *s1, const void *s2, size_t n); extern void *memchr(const void *s, int c, size_t n); // Non-POSIX; GNU extension -// Note: these specifications are simplified w.r.t memchr's; in particular, -// since we start searching from the end of the string, we assume the entire -// string is valid, initialized, non-escaping, etc. +// Note: these specifications are simplified w.r.t memchr's /*@ requires valid: valid_read_or_empty(s, n); requires initialization: \initialized(((unsigned char*)s)+(0 .. n - 1)); @@ -98,8 +97,8 @@ extern void *memchr(const void *s, int c, size_t n); assigns \result \from s, indirect:c, indirect:((unsigned char*)s)[0..n-1]; behavior found: assumes char_found: memchr((char*)s,c,n); + ensures result_valid_read: \valid_read((char*)\result); ensures result_same_base: \base_addr(\result) == \base_addr(s); - ensures result_points_to_same_base: \subset(\result, s+(0 .. n)); behavior not_found: assumes char_not_found: !memchr((char*)s,c,n); ensures result_null: \result == \null; @@ -170,7 +169,8 @@ extern size_t strnlen (const char *s, size_t n); /*@ requires valid_string_s1: valid_read_string(s1); @ requires valid_string_s2: valid_read_string(s2); - @ assigns \result \from indirect:s1[0..], indirect:s2[0..]; + @ assigns \result \from indirect:s1[0..strlen(s1)], + @ indirect:s2[0..strlen(s1)]; @ ensures acsl_c_equiv: \result == strcmp(s1,s2); @*/ extern int strcmp (const char *s1, const char *s2); @@ -184,189 +184,221 @@ extern int strncmp (const char *s1, const char *s2, size_t n); /*@ requires valid_string_s1: valid_read_string(s1); // over-strong @ requires valid_string_s2: valid_read_string(s2); // over-strong - @ assigns \result \from indirect:s1[0..], indirect:s2[0..]; + @ assigns \result \from indirect:s1[0..strlen(s1)], + @ indirect:s2[0..strlen(s2)]; //missing: \from 'locale' @*/ extern int strcoll (const char *s1, const char *s2); -/*@ requires valid_string_s: valid_read_string(s); - @ assigns \result \from s, indirect:s[0..strlen(s)], indirect:c; - @ behavior found: - @ assumes char_found: strchr(s,c); - @ ensures result_char: *\result == (char)c; - @ ensures result_same_base: \base_addr(\result) == \base_addr(s); - @ ensures result_in_length: s <= \result <= s + strlen(s); - @ ensures result_valid_string: valid_read_string(\result); - @ ensures result_first_occur: \forall char* p; s<=p<\result ==> *p != (char)c; - @ behavior not_found: - @ assumes char_not_found: !strchr(s,c); - @ ensures result_null: \result == \null; - @ behavior default: - @ ensures result_null_or_same_base: - @ \result == \null || \base_addr(\result) == \base_addr(s); - @*/ +/*@ + requires valid_string_s: valid_read_string(s); + assigns \result \from s, indirect:s[0..strlen(s)], indirect:c; + behavior found: + assumes char_found: strchr(s,c); + ensures result_valid_string: valid_read_string(\result); + ensures result_char: *\result == (char)c; + ensures result_same_base: \base_addr(\result) == \base_addr(s); + ensures result_in_length: s <= \result <= s + strlen(s); + ensures result_first_occurrence: + \forall char* p; s <= p < \result ==> *p != (char)c; + behavior not_found: + assumes char_not_found: !strchr(s,c); + ensures result_null: \result == \null; + complete behaviors; + disjoint behaviors; +*/ extern char *strchr(const char *s, int c); -/*@ requires valid_string_s: valid_read_string(s); - @ assigns \result \from s, indirect:s[0..strlen(s)], indirect:c; - @ ensures result_same_base: \subset(\result, s+(0..strlen(s))); - @*/ +/*@ + requires valid_string_s: valid_read_string(s); + assigns \result \from s, indirect:s[0..strlen(s)], indirect:c; + ensures result_valid_string: valid_read_string(\result); + ensures result_same_base: \base_addr(\result) == \base_addr(s); +*/ extern char *strchrnul(const char *s, int c); -/*@ requires valid_string_s: valid_read_string(s); - @ assigns \result \from s, indirect:s[0 .. strlen(s)], indirect:c; - @ behavior found: - @ assumes char_found: strchr(s,c); - @ ensures result_char: *\result == c; - @ ensures result_same_base: \base_addr(\result) == \base_addr(s); - @ ensures result_valid_string: valid_read_string(\result); - @ behavior not_found: - @ assumes char_not_found: !strchr(s,c); - @ ensures result_null: \result == \null; - @ behavior default: - @ ensures result_null_or_same_base: - @ \result == \null || \base_addr(\result) == \base_addr(s); - @ ensures result_null_or_points_to_same: - @ \result == \null || \subset(\result, s + (0 .. strlen(s))); - @*/ +/*@ + requires valid_string_s: valid_read_string(s); + assigns \result \from s, indirect:s[0 .. strlen(s)], indirect:c; + ensures result_null_or_same_base: + \result == \null || + (valid_read_string(\result) && \base_addr(\result) == \base_addr(s)); +*/ extern char *strrchr(const char *s, int c); -/*@ requires valid_string_s: valid_read_string(s); - @ requires valid_string_reject: valid_read_string(reject); - @ assigns \result \from indirect:s[0..], indirect:reject[0..]; - @ ensures result_bounded: 0 <= \result <= strlen(s); - @*/ +/*@ + requires valid_string_s: valid_read_string(s); + requires valid_string_reject: valid_read_string(reject); + assigns \result \from indirect:s[0..strlen(s)], + indirect:reject[0..strlen(reject)]; + ensures result_bounded: 0 <= \result <= strlen(s); +*/ extern size_t strcspn(const char *s, const char *reject); -/*@ requires valid_string_s: valid_read_string(s); - @ requires valid_string_accept: valid_read_string(accept); - @ assigns \result \from s[0..], accept[0..]; - @ assigns \result \from indirect:s[0..], indirect:accept[0..]; - @ ensures result_bounded: 0 <= \result <= strlen(s); - @*/ +/*@ + requires valid_string_s: valid_read_string(s); + requires valid_string_accept: valid_read_string(accept); + assigns \result \from indirect:s[0..strlen(s)], + indirect:accept[0..strlen(accept)]; + ensures result_bounded: 0 <= \result <= strlen(s); +*/ extern size_t strspn(const char *s, const char *accept); -/*@ requires valid_string_s: valid_read_string(s); - @ requires valid_string_accept: valid_read_string(accept); - @ assigns \result \from s, s[0..], accept[0..]; - @ ensures result_null_or_same_base: - @ \result == \null || \base_addr(\result) == \base_addr(s); - @*/ +/*@ + requires valid_string_s: valid_read_string(s); + requires valid_string_accept: valid_read_string(accept); + assigns \result \from s, indirect:s[0..strlen(s)], + indirect:accept[0..strlen(accept)]; + ensures result_null_or_same_base: + \result == \null || + (valid_read_string(\result) && \base_addr(\result) == \base_addr(s)); +*/ extern char *strpbrk(const char *s, const char *accept); -/*@ requires valid_string_haystack: valid_read_string(haystack); - @ requires valid_string_needle: valid_read_string(needle); - @ assigns \result \from haystack, indirect:haystack[0..], - @ indirect:needle[0..]; - @ ensures result_null_or_in_haystack: - @ \result == \null - @ || (\subset(\result, haystack+(0..)) && \valid_read(\result) - @ && memcmp{Pre,Pre}(\result,needle,strlen(needle)) == 0); - @*/ +/*@ + requires valid_string_haystack: valid_read_string(haystack); + requires valid_string_needle: valid_read_string(needle); + assigns \result \from haystack, indirect:haystack[0..strlen(haystack)], + indirect:needle[0..strlen(needle)]; + ensures result_null_or_in_haystack: + \result == \null || + (valid_read_string(\result) && + \base_addr(\result) == \base_addr(haystack) && + memcmp{Pre,Pre}(\result,needle,strlen(needle)) == 0); +*/ extern char *strstr(const char *haystack, const char *needle); -/*@ requires valid_string_haystack: valid_read_string(haystack); - @ requires valid_string_needle: valid_read_string(needle); - @ assigns \result \from haystack, indirect:haystack[0..], - @ indirect:needle[0..]; - @ ensures result_null_or_in_haystack: - @ \result == \null - @ || (\subset(\result, haystack+(0..)) && \valid_read(\result)); - @*/ -extern char *strcasestr (const char *haystack, const char *needle); +/*@ + requires valid_string_haystack: valid_read_string(haystack); + requires valid_string_needle: valid_read_string(needle); + assigns \result \from haystack, indirect:haystack[0..strlen(haystack)], + indirect:needle[0..strlen(needle)]; + ensures result_null_or_in_haystack: + \result == \null || + (valid_read_string(\result) && + \base_addr(\result) == \base_addr(haystack)); + // Note: there is currently no way to concisely write 'memcmp_ignore_case' +*/ +extern char *strcasestr(const char *haystack, const char *needle); // internal state of strtok char *__fc_strtok_ptr; -/*@ // missing: separation +/*@ requires valid_string_delim: valid_read_string(delim); + requires separation: \separated(delim+(0..strlen(delim)), s+(0..strlen(s))); assigns s[0..] \from s[0..], - indirect:s, indirect:__fc_strtok_ptr, indirect:delim[0..]; + indirect:s, indirect:__fc_strtok_ptr, indirect:delim[0..strlen(delim)]; assigns __fc_strtok_ptr[0..] \from __fc_strtok_ptr[0..], - indirect:s, indirect:__fc_strtok_ptr, indirect:delim[0..]; + indirect:s, indirect:__fc_strtok_ptr, indirect:delim[0..strlen(delim)]; assigns \result \from s, __fc_strtok_ptr, indirect:s[0..], - indirect:__fc_strtok_ptr[0..], indirect:delim[0..]; + indirect:__fc_strtok_ptr[0..], indirect:delim[0..strlen(delim)]; assigns __fc_strtok_ptr \from \old(__fc_strtok_ptr), s, indirect:__fc_strtok_ptr[0..], - indirect:delim[0..]; + indirect:delim[0..strlen(delim)]; behavior new_str: assumes s_not_null: s != \null; requires valid_string_s_or_delim_not_found: valid_string(s) || (valid_read_string(s) && - \forall int i; 0 <= i < strlen(delim) ==> !strchr(s,delim[i])); - assigns __fc_strtok_ptr \from s, indirect:s[0..], indirect:delim[0..]; - assigns s[0..] \from s[0..], indirect:s, indirect:delim[0..]; - assigns \result \from s, indirect:s[0..], indirect:delim[0..]; - ensures result_subset: \result == \null || \subset(\result, s+(0..)); - ensures ptr_subset: \subset(__fc_strtok_ptr, s+(0..)); + \forall ℤ i; 0 <= i < strlen(delim) ==> !strchr(s,delim[i])); + assigns __fc_strtok_ptr + \from s, indirect:s[0..], indirect:delim[0..strlen(delim)]; + assigns s[0..] \from s[0..], indirect:s, indirect:delim[0..strlen(delim)]; + assigns \result \from s, indirect:s[0..], indirect:delim[0..strlen(delim)]; + ensures result_same_base: + \result == \null || + (valid_read_string(\result) && \base_addr(\result) == \base_addr(s)); + ensures ptr_valid_string: valid_read_string(__fc_strtok_ptr); + ensures ptr_same_base: \base_addr(__fc_strtok_ptr) == \base_addr(s); behavior resume_str: assumes s_null: s == \null; requires not_first_call: __fc_strtok_ptr != \null; assigns __fc_strtok_ptr[0..] \from __fc_strtok_ptr[0..], indirect:__fc_strtok_ptr, - indirect:delim[0..]; + indirect:delim[0..strlen(delim)]; assigns __fc_strtok_ptr \from \old(__fc_strtok_ptr), indirect:__fc_strtok_ptr[0..], - indirect:delim[0..]; + indirect:delim[0..strlen(delim)]; assigns \result \from __fc_strtok_ptr, indirect:__fc_strtok_ptr[0..], - indirect:delim[0..]; - ensures result_subset: \result == \null || - \subset(\result, \old(__fc_strtok_ptr)+(0..)); - ensures ptr_subset: \subset(__fc_strtok_ptr, \old(__fc_strtok_ptr)+(0..)); + indirect:delim[0..strlen(delim)]; + ensures result_same_base: + \result == \null || + (valid_read_string(\result) && + \base_addr(\result) == \base_addr(\old(__fc_strtok_ptr))); + ensures ptr_valid_string: valid_read_string(__fc_strtok_ptr); + ensures ptr_same_base: + \base_addr(__fc_strtok_ptr) == \base_addr(\old(__fc_strtok_ptr)); complete behaviors; disjoint behaviors; */ extern char *strtok(char *restrict s, const char *restrict delim); -/*@ // missing: separation +/*@ requires valid_string_delim: valid_read_string(delim); + requires separation: \separated(delim+(0..strlen(delim)), s+(0..strlen(s)), + *saveptr+(0..)); requires valid_saveptr: \valid(saveptr); assigns s[0..] \from s[0..], - indirect:s, indirect:*saveptr, indirect:delim[0..]; + indirect:s, indirect:*saveptr, indirect:delim[0..strlen(delim)]; assigns (*saveptr)[0..] \from (*saveptr)[0..], - indirect:s, indirect:*saveptr, indirect:delim[0..]; + indirect:s, indirect:*saveptr, indirect:delim[0..strlen(delim)]; assigns \result \from s, *saveptr, indirect:s[0..], - indirect:(*saveptr)[0..], indirect:delim[0..]; + indirect:(*saveptr)[0..], indirect:delim[0..strlen(delim)]; assigns *saveptr \from \old(*saveptr), s, indirect:(*saveptr)[0..], - indirect:delim[0..]; + indirect:delim[0..strlen(delim)]; behavior new_str: assumes s_not_null: s != \null; requires valid_string_s_or_delim_not_found: valid_string(s) || (valid_read_string(s) && - \forall int i; 0 <= i < strlen(delim) ==> !strchr(s,delim[i])); - assigns *saveptr \from s, indirect:s[0..], indirect:delim[0..]; - assigns s[0..] \from s[0..], indirect:s, indirect:delim[0..]; - assigns \result \from s, indirect:s[0..], indirect:delim[0..]; - ensures result_subset: \result == \null || \subset(\result, s+(0..)); - ensures initialization: \initialized(saveptr); - ensures saveptr_subset: \subset(*saveptr, s+(0..)); + \forall ℤ i; 0 <= i < strlen(delim) ==> !strchr(s,delim[i])); + assigns *saveptr + \from s, indirect:s[0..], indirect:delim[0..strlen(delim)]; + assigns s[0..] \from s[0..], indirect:s, indirect:delim[0..strlen(delim)]; + assigns \result \from s, indirect:s[0..], indirect:delim[0..strlen(delim)]; + ensures result_same_base: + \result == \null || + (valid_read_string(\result) && \base_addr(\result) == \base_addr(s)); + ensures saveptr_valid_string: valid_read_string(*saveptr); + ensures saveptr_same_base: \base_addr(*saveptr) == \base_addr(s); behavior resume_str: assumes s_null: s == \null; requires not_first_call: *saveptr != \null; requires initialization:saveptr: \initialized(saveptr); assigns (*saveptr)[0..] \from (*saveptr)[0..], indirect:*saveptr, - indirect:delim[0..]; + indirect:delim[0..strlen(delim)]; assigns *saveptr \from \old(*saveptr), indirect:(*saveptr)[0..], - indirect:delim[0..]; + indirect:delim[0..strlen(delim)]; assigns \result \from *saveptr, indirect:(*saveptr)[0..], - indirect:delim[0..]; - ensures result_subset: \result == \null || - \subset(\result, \old(*saveptr)+(0..)); - ensures saveptr_subset: \subset(*saveptr, \old(*saveptr)+(0..)); + indirect:delim[0..strlen(delim)]; + ensures result_same_base: + \result == \null || + (valid_read_string(\result) && + \base_addr(\result) == \base_addr(\old(*saveptr))); + ensures saveptr_valid_string: valid_read_string(*saveptr); + ensures saveptr_same_base: + \base_addr(*saveptr) == \base_addr(\old(*saveptr)); complete behaviors; disjoint behaviors; */ extern char *strtok_r(char *restrict s, const char *restrict delim, char **restrict saveptr); -/*@ requires valid_string_stringp: \valid(stringp) && valid_string(*stringp); - @ requires valid_string_delim: valid_read_string(delim); - @ assigns *stringp \from delim[..], *stringp[..]; - @ assigns \result \from delim[..], *stringp[..]; - @*/ +/*@ + requires valid_stringp: \valid(stringp); + assigns *stringp[0..] \from indirect:delim[0..strlen(delim)], *stringp[0..]; + assigns \result \from *stringp; + behavior no_stringp: + assumes stringp_null: *stringp == \null; + ensures result_null: \result == \null; + behavior valid_stringp: + assumes stringp_not_null: *stringp != \null; + requires valid_string_stringp: valid_string(*stringp); + ensures valid_result: valid_string(\result) && + \base_addr(\result) == \base_addr(*stringp); +*/ extern char *strsep (char **stringp, const char *delim); __FC_EXTERN char __fc_strerror[64]; @@ -440,10 +472,12 @@ size_t strlcpy(char * restrict dest, const char * restrict src, size_t n); @*/ extern char *stpcpy(char *restrict dest, const char *restrict src); -/*@ // missing: separation +/*@ @ requires valid_string_src: valid_read_string(src); @ requires valid_string_dest: valid_string(dest); @ requires room_string: \valid(dest+(0..strlen(dest) + strlen(src))); + @ requires separation: + @ \separated(dest+(0..strlen(dest)+strlen(src)), src+(0..strlen(src))); @ assigns dest[strlen(dest)..strlen(dest) + strlen(src)] @ \from src[0..strlen(src)]; @ ensures sum_of_lengths: strlen(dest) == \old(strlen(dest) + strlen(src)); @@ -455,49 +489,60 @@ extern char *stpcpy(char *restrict dest, const char *restrict src); @*/ extern char *strcat(char *restrict dest, const char *restrict src); -/*@ // missing: separation - @ requires valid_nstring_src: valid_read_nstring(src, n); - @ requires valid_string_dest: valid_string(dest); - @ assigns dest[strlen(dest) .. strlen(dest) + n] \from src[0..n]; - @ assigns \result \from dest; - @ ensures result_ptr: \result == dest; - @ behavior complete: - @ assumes valid_string_src_fits: valid_read_string(src) && strlen(src) <= n; - @ requires room_string: \valid(dest + strlen(dest) + (0 .. strlen(src))); - @ assigns dest[strlen(dest)..strlen(dest) + strlen(src)] - @ \from src[0..strlen(src)]; - @ assigns \result \from dest; - @ ensures sum_of_lengths: strlen(dest) == \old(strlen(dest) + strlen(src)); - @ behavior partial: - @ assumes valid_string_src_too_large: - @ !(valid_read_string(src) && strlen(src) <= n); - @ requires room_string: \valid(dest + strlen(dest) + (0 .. n)); - @ assigns dest[strlen(dest)..strlen(dest) + n] - @ \from src[0..strlen(src)]; - @ assigns \result \from dest; - @ ensures sum_of_bounded_lengths: strlen(dest) == \old(strlen(dest)) + n; - @*/ +// ISO C23, 7.26.3.2, footnote 378: +// "A terminating null character is always appended to the result. +// Thus, the maximum number of characters that can end up in the array pointed +// to by s1 is strlen(s1)+n+1" +/*@ + requires valid_nstring_src: valid_read_nstring(src, n); + requires valid_string_dest: valid_string(dest); + requires separation: + \separated(dest+(0..strlen(dest)+strnlen(src, n)), + src+(0..strnlen(src, n))); + assigns dest[strlen(dest) .. strlen(dest) + n] \from src[0 .. n-1]; + assigns \result \from dest; + ensures result_ptr: \result == dest; + behavior complete: + assumes src_fits: strnlen(src, n) < n; // strnlen(src, n) == strlen(src) + requires room_string: \valid(dest + strlen(dest) + (0 .. strlen(src))); + assigns dest[strlen(dest) .. strlen(dest) + strlen(src)] + \from src[0 .. strlen(src)-1]; + assigns \result \from dest; + ensures sum_of_lengths: strlen(dest) == \old(strlen(dest) + strlen(src)); + behavior partial: + assumes src_too_large: strnlen(src, n) == n; + requires room_string: \valid(dest + strlen(dest) + (0 .. n)); + assigns dest[strlen(dest) .. strlen(dest) + n] + \from src[0 .. n - 1]; + assigns \result \from dest; + ensures sum_of_bounded_lengths: strlen(dest) == \old(strlen(dest)) + n; + complete behaviors; + disjoint behaviors; +*/ extern char *strncat(char *restrict dest, const char *restrict src, size_t n); /*@ // Non-POSIX, but often present - @ // missing: separation + @ @ requires valid_string_src: valid_read_string(src); @ requires valid_string_dest: valid_string(dest); @ requires room_nstring: \valid(dest+(0..n-1)); + @ requires separation: + @ \separated(dest+(0..\min(n-1, strlen(dest)+strlen(src))), + @ src+(0..strnlen(src, n-1))); @ assigns dest[strlen(dest)..n] \from indirect:n, src[0..strlen(src)]; @ assigns \result \from indirect:src, indirect:src[0..n-1], indirect:n; @ ensures bounded_result: \result == strlen(dest) + strlen(src); @*/ extern size_t strlcat(char *restrict dest, const char *restrict src, size_t n); -/*@ // missing: separation - @ requires valid_dest: \valid(dest+(0..n - 1)); - @ requires valid_string_src: valid_read_string(src); - @ assigns dest[0..n - 1] \from indirect:src[0..], indirect:n; - @ assigns \result \from dest; - @*/ -extern size_t strxfrm (char *restrict dest, - const char *restrict src, size_t n); +/*@ // missing: assigns ... \from 'locale'; + requires valid_dest: \valid(dest+(0..n - 1)); + requires valid_string_src: valid_read_string(src); + requires separation: \separated(dest+(0..n - 1), src+(0..strlen(src))); + assigns dest[0..n - 1] \from src[0..strlen(src)], indirect:n; + assigns \result \from indirect:src[0..strlen(src)]; // can be greater than n +*/ +extern size_t strxfrm(char *restrict dest, const char *restrict src, size_t n); // Non-POSIX; GNU extension /*@ @@ -516,56 +561,84 @@ extern void *memmem(const void *haystack, size_t haystacklen, // Allocate strings -/*@ requires valid_string_s: valid_read_string(s); - @ allocates \result; - @ assigns \result \from indirect:s[0..strlen(s)], indirect:__fc_heap_status; - @ behavior allocation: - @ assumes can_allocate: is_allocable(strlen(s)); - @ assigns __fc_heap_status \from indirect:s, __fc_heap_status; - @ assigns \result \from indirect:s[0..strlen(s)], indirect:__fc_heap_status; - @ ensures allocation: \fresh(\result,strlen(s)); - @ ensures result_valid_string_and_same_contents: - @ valid_string(\result) && strcmp(\result,s) == 0; - @ behavior no_allocation: - @ assumes cannot_allocate: !is_allocable(strlen(s)); - @ allocates \nothing; - @ assigns \result \from \nothing; - @ ensures result_null: \result == \null; - @*/ +/*@ + requires valid_string_s: valid_read_string(s); + allocates \result; + assigns \result \from indirect:s[0..strlen(s)], indirect:__fc_heap_status; + behavior allocation: + assumes can_allocate: is_allocable(strlen(s)+1); + assigns __fc_heap_status \from __fc_heap_status, + indirect:s[0 .. strlen(s)]; + assigns \result \from indirect:s[0..strlen(s)], indirect:__fc_heap_status; + ensures allocation: \fresh(\result,strlen(s)+1); + ensures result_valid_string_and_same_contents: + valid_string(\result) && strcmp(\result,s) == 0; + behavior no_allocation: + assumes cannot_allocate: !is_allocable(strlen(s)+1); + allocates \nothing; + assigns \result \from \nothing; + ensures result_null: \result == \null; +*/ extern char *strdup (const char *s); -/*@ requires valid_string_s: valid_read_string(s); - @ allocates \result; - @ assigns \result \from indirect:s[0..strlen(s)], indirect:n, - @ indirect:__fc_heap_status; - @ behavior allocation: - @ assumes can_allocate: is_allocable(\min(strlen(s), n+1)); - @ assigns __fc_heap_status \from indirect:s, indirect:n, __fc_heap_status; - @ assigns \result \from indirect:s[0..strlen(s)], indirect:n, - @ indirect:__fc_heap_status; - @ ensures allocation: \fresh(\result,\min(strlen(s), n+1)); - @ ensures result_valid_string_bounded_and_same_prefix: - @ \valid(\result+(0..\min(strlen(s),n))) && - @ valid_string(\result) && strlen(\result) <= n && - @ strncmp(\result,s,n) == 0; - @ behavior no_allocation: - @ assumes cannot_allocate: !is_allocable(\min(strlen(s), n+1)); - @ allocates \nothing; - @ assigns \result \from \nothing; - @ ensures result_null: \result == \null; - @*/ +// From the strndup(3p) POSIX man page: +// ... strndup() copies at most size plus one bytes into the newly allocated +// memory, terminating the new string with a NUL character. +/*@ + requires valid_string_s: valid_read_string(s); + allocates \result; + assigns \result \from indirect:s[0..strlen(s)], indirect:n, + indirect:__fc_heap_status; + behavior allocation: + assumes can_allocate: is_allocable(strnlen(s, n) + 1); + assigns __fc_heap_status \from indirect:s, indirect:n, __fc_heap_status; + assigns \result \from indirect:s[0 .. strlen(s)], indirect:n, + indirect:__fc_heap_status; + ensures allocation: \fresh(\result, strnlen(s, n) + 1); + ensures result_valid_string_bounded_and_same_prefix: + \valid(\result+(0 .. strnlen(s,n))) && + valid_string(\result) && strlen(\result) <= n && + strncmp(\result,s,n) == 0; + behavior no_allocation: + assumes cannot_allocate: !is_allocable(strnlen(s, n) + 1); + allocates \nothing; + assigns \result \from \nothing; + ensures result_null: \result == \null; +*/ extern char *strndup (const char *s, size_t n); // More POSIX, non-C99 functions +/*@ + requires valid_string_src: valid_read_nstring(src, n); + requires valid_dest: \valid(dest+(0 .. n-1)); + requires separation: \separated(src+(0 .. n-1), dest+(0 .. n-1)); + assigns dest[0 .. n-1] \from src[0 .. n-1], indirect:n; + assigns \result \from dest, indirect:src[0 .. n-1], indirect:n; + ensures initialization: \initialized(dest+(0 .. n-1)); + behavior src_is_a_string: + assumes src_has_nul: strlen(src) < n; + ensures valid_string_dest: valid_read_string(dest); + ensures same_contents: strcmp(dest, src) == 0; + ensures dest_nul_padded: + \forall integer i; strlen(src) <= i < n ==> dest[i] == 0; + ensures result_points_to_nul: \result == dest + strlen(src); + behavior src_too_long: + assumes src_no_nul: \forall integer i; 0 <= i < n ==> src[i] != 0; + ensures same_partial_contents: strncmp(src, dest, n) == 0; + ensures result_points_to_end: \result == dest + n; +*/ extern char *stpncpy(char *restrict dest, const char *restrict src, size_t n); + //extern int strcoll_l(const char *s1, const char *s2, locale_t locale); + //extern char *strerror_l(int errnum, locale_t locale); + extern int strerror_r(int errnum, char *strerrbuf, size_t buflen); __FC_EXTERN char __fc_strsignal[64]; char * const __fc_p_strsignal = __fc_strsignal; -/*@ //missing: requires valid_signal(signum); +/*@ //missing: requires valid_signal(signum); assigns \from 'locale'; @ assigns \result \from __fc_p_strsignal, indirect:signum; @ ensures result_internal_str: \result == __fc_p_strsignal; @ ensures result_nul_terminated: \result[63] == 0; diff --git a/share/libc/wchar.h b/share/libc/wchar.h index c3a5d51c94b..38bcc2d98cc 100644 --- a/share/libc/wchar.h +++ b/share/libc/wchar.h @@ -273,7 +273,7 @@ extern int wcscasecmp(const wchar_t *ws1, const wchar_t *ws2); assigns \result \from indirect:ws[0..wcslen(ws)], indirect:__fc_heap_status; behavior allocation: assumes can_allocate: is_allocable(wcslen(ws)); - assigns __fc_heap_status \from indirect:ws[0 .. wcslen(ws), + assigns __fc_heap_status \from indirect:ws[0 .. wcslen(ws)], __fc_heap_status; assigns \result \from indirect:ws[0..wcslen(ws)], indirect:__fc_heap_status; ensures allocation: \fresh(\result,wcslen(ws) * sizeof(wchar_t)); diff --git a/tests/libc/stdlib_c_env.c b/tests/libc/stdlib_c_env.c index 65fc13a16cc..12bce8dbf2c 100644 --- a/tests/libc/stdlib_c_env.c +++ b/tests/libc/stdlib_c_env.c @@ -23,5 +23,9 @@ int main() { //@ assert i7 == -1; // contains '=' int i8 = unsetenv(r2); char *r3 = getenv(r2); + int i9 = setenv("BLA=", "val", 0); + //@ check i9 == -1; // contains '=' + int i10 = setenv("", "val", 0); + //@ check i10 == -1; // empty name return 0; } diff --git a/tests/libc/stdlib_h.c b/tests/libc/stdlib_h.c index 7c9aa731e60..a29dc625702 100644 --- a/tests/libc/stdlib_h.c +++ b/tests/libc/stdlib_h.c @@ -12,6 +12,8 @@ int compare_int(const void *a, const void *b) { return (*(int*)a < *(int*)b) ? -1 : (*(int*)a > *(int*)b); } +char putenv_buf[30] = "PATH3=/:/foo:/bar:"; + volatile int nondet; int main() { int base = nondet ? 0 : nondet ? 2 : 36; @@ -122,5 +124,26 @@ int main() { l = lrand48(); //@ assert 0 <= l < 2147483648; + char *path = getenv("PATH"); + if (path) { + //@ check imprecise: valid_read_string(path); + } + int setenv_res = setenv("PATH2", "/", 0); + //@ check setenv_res == -1 || setenv_res == 0; + int putenv_res = putenv(putenv_buf); + + div_t div_res; + if (nondet) { + div_res = div(1, 0); + //@ check unreachable: \false; + } + if (nondet) { + div_res = div(INT_MIN, -1); + //@ check unreachable: \false; + } + div_res = div(INT_MAX, -1); + + ldiv_t ldiv_res = ldiv(LONG_MAX, -1); + lldiv_t lldiv_res = lldiv(LLONG_MAX, -1); return 0; } diff --git a/tests/libc/string_h.c b/tests/libc/string_h.c index 090b9f60561..471e6e3a08b 100644 --- a/tests/libc/string_h.c +++ b/tests/libc/string_h.c @@ -3,7 +3,7 @@ void test_strcmp(void) { int res = strcmp("hello", "world"); - //@ assert res == 0; + //@ check res == 0; } void test_strcat(void) @@ -22,7 +22,7 @@ void test_strstr(void) char *s = nondet ? "aba" : "bab"; char *needle = nondet ? "a" : "b"; char *res = strstr(s, needle); - //@ assert res != 0; + //@ check res != 0; } void test_strncat(void) @@ -34,6 +34,16 @@ void test_strncat(void) for (int i = 0; i < 99; i++) source[i] = 'Z'; source[99] = '\0'; strncat(data, source, 100); + + // index: 0 1 2 3 4 5 6 7 + char buf1[] = { 'a', 'b', 0, 'c', 'd', 0, 'f', 'g' }; + strncat(buf1+3, buf1, 2); // valid: 'ab\0cdab\0' + + if (nondet) { + char buf1[] = { 'a', 'b', 0, 'c', 'd', 0, 'f' }; + strncat(buf1+3, buf1, 2); // invalid: strncat will add a final '\0' + //@ assert unreachable: \false; + } } struct s { @@ -60,17 +70,17 @@ void test_strtok() { } char buf[2] = {0}; char *a = strtok(buf, " "); - //@ assert a == \null || \subset(a, buf+(0..)); + //@ check a == \null || \subset(a, buf+(0..)); char *b = strtok(NULL, " "); - //@ assert b == \null || \subset(b, buf+(0..)); + //@ check b == \null || \subset(b, buf+(0..)); char buf2[4] = "abc"; char *p = strtok(buf2, "b"); - //@ assert p == \null || \subset(p, buf2+(0..)); + //@ check p == \null || \subset(p, buf2+(0..)); char *q = strtok(NULL, "c"); - //@ assert q == \null || \subset(p, buf2+(0..)); + //@ check q == \null || \subset(p, buf2+(0..)); // test with non-writable string, but delimiter not found char *r = strtok((char*)"constant!", "NONE_TO_BE_FOUND"); - //@ assert r == \null; + //@ check r == \null; if (nondet) { strtok((char*)"constant!", "!"); //@ assert unreachable_if_precise: \false; @@ -89,18 +99,18 @@ void test_strtok_r() { strtok_r(buf, " ", NULL); // must fail //@ assert unreachable: \false; } - //@ assert a == \null || \subset(a, buf+(0..)); + //@ check a == \null || \subset(a, buf+(0..)); char *b = strtok_r(NULL, " ", &saveptr); Frama_C_show_each_saveptr(saveptr); - //@ assert b == \null || \subset(b, buf+(0..)); + //@ check b == \null || \subset(b, buf+(0..)); char buf2[4] = "abc"; char *p = strtok_r(buf2, "b", &saveptr); - //@ assert p == \null || \subset(p, buf2+(0..)); + //@ check p == \null || \subset(p, buf2+(0..)); char *q = strtok_r(NULL, "c", &saveptr); - //@ assert q == \null || \subset(p, buf2+(0..)); + //@ check q == \null || \subset(p, buf2+(0..)); // test with non-writable string, but delimiter not found char *r = strtok_r((char*)"constant!", "NONE_TO_BE_FOUND", &saveptr); - //@ assert r == \null; + //@ check r == \null; if (nondet) { strtok_r((char*)"constant!", "!", &saveptr); //@ assert unreachable_if_precise: \false; @@ -154,20 +164,20 @@ int main(int argc, char **argv) char *a = strdup("bla"); // unsound; specification currently unsupported char *b = strndup("bla", 2); // unsound; specification currently unsupported char *strsig = strsignal(1); - //@ assert valid_read_string(strsig); + //@ check valid_read_string(strsig); test_strncpy(); test_strlcpy(); test_strrchr(); char *c = "haystack"; char d = nondet ? 'y' : 'k'; - char *chr1 = strchr(c, d); + char *chr1 = strchr(c, d); //@ check *chr1 == d; char *nul1 = strchrnul(c, d); d = nondet ? 'a' : 'n'; char *chr2 = strchr(c, d); char *nul2 = strchrnul(c, d); char pdest[10]; char *pend = mempcpy(pdest, "gnu-only function", 9); - //@ assert imprecise: pend == pdest + 9 && *pend == '\0'; + //@ check imprecise: pend == pdest + 9 && *pend == '\0'; char *rchr = memrchr(c, 'a', strlen(c)); //@ check imprecise: rchr == c + strlen("haysta"); @@ -184,5 +194,52 @@ int main(int argc, char **argv) memmem(mm_haystack, sizeof(mm_haystack), mm_needle2, sizeof(mm_needle2)); //@ check imprecise: memm == \null; + char strsep_buf[8] = "a,b,,cc", *strsep_p1 = &strsep_buf[0]; + char strsep_needle[] = ",./"; + char *strsep_p2 = strsep(&strsep_p1, strsep_needle); + //@ check \base_addr(strsep_p1) == \base_addr(strsep_p2); + strsep_p2 = strsep(&strsep_p1, strsep_needle); + //@ check \base_addr(strsep_p1) == \base_addr(strsep_p2); + char *strsep_null = NULL; + strsep_p2 = strsep(&strsep_null, strsep_needle); + //@ check strsep_p2 == \null; + + char *stpncpy_src = "12345678"; + char stpncpy_dest[5]; + char *stpncpy_res = stpncpy(stpncpy_dest, stpncpy_src, 5); + //@ check stpncpy_res == stpncpy_dest + 5; + //@ check \forall integer i; 0 <= i < 5 ==> stpncpy_dest[i] != 0; + char *stpncpy_src2 = "12"; + stpncpy_res = stpncpy(stpncpy_dest, stpncpy_src2, 5); + //@ check stpncpy_dest[0] == '1' && stpncpy_dest[1] == '1'; + //@ check stpncpy_dest[2..4] == 0; + //@ check stpncpy_res == stpncpy_dest + 2; + + char *strlcat_src = "cat"; + char strlcat_buf[10] = "dog"; + size_t strlcat_res = strlcat(strlcat_buf, strlcat_src, 10); // "dogcat" + //@ check imprecise: strlcat_res == 6; + strlcat_res = strlcat(strlcat_buf, strlcat_buf, 0); // no overlap: n too small + char strlcat_buf2[10] = "dogcat"; + strlcat_buf2[3] = 0; // "dog\0at" + if (nondet) { + // must fail: overlap between 'future string' "dogat" and source "at" + strlcat_res = strlcat(strlcat_buf2, strlcat_buf2 + 4, 10); // overlap + //@ assert unreachable: \false; + } + // no overlap: n too small; no changes to buffer + strlcat_res = strlcat(strlcat_buf2, strlcat_buf2 + 4, 4); + char strlcat_buf3[10] = "dogcat"; + strlcat_buf3[3] = 0; // "dog\0at" + // no overlap: "dogt\0" and "t" + strlcat_res = strlcat(strlcat_buf3, strlcat_buf3 + 5, 10); + // check imprecise: strlcat_res == 5; + + char *strxfrm_src = "harr"; + // dest can be NULL if length is zero + size_t strxfrm_res = strxfrm(0, strxfrm_src, 0); + char strxfrm_dest[10]; + strxfrm_res = strxfrm(strxfrm_dest, strxfrm_src, 10); + return 0; } -- GitLab