C library: Refine and improve stdio models#8043
C library: Refine and improve stdio models#8043tautschnig wants to merge 2 commits intodiffblue:developfrom
Conversation
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## develop #8043 +/- ##
========================================
Coverage 80.39% 80.40%
========================================
Files 1688 1688
Lines 207403 207413 +10
Branches 73 73
========================================
+ Hits 166749 166769 +20
+ Misses 40654 40644 -10 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
d479dc1 to
3917e27
Compare
3917e27 to
85ca3e5
Compare
3d5bb00 to
22e9222
Compare
22e9222 to
94cbfa2
Compare
e7bf25e to
e0c978c
Compare
e0c978c to
49f4cd1
Compare
BSD systems appear to use `__` prefixed variants of several functions. Define these as needed. Also, avoid handling some GCC-style `__builtin_`-prefixed functions via models when others are done directly in the type checker: do all of them in the type checker.
Fixes portability to FreeBSD, which redefines several functions as macros that would only conditionally call that function. Also, ensure that stdin/stdout/stderr point to valid objects when those are fdopen'ed.
49f4cd1 to
aa95077
Compare
There was a problem hiding this comment.
Pull request overview
Refines CBMC’s C-library stdio/math models to improve portability (notably on BSDs where some stdio/math functions are macros) and to ensure stdin/stdout/stderr refer to valid objects when created via fdopen.
Changes:
- Adjust stdio models (
fdopen, macro#undefs, andfclose) to avoid macro collisions and invalid frees of standard streams. - Expand math support for builtin/macro variants (
__isfinite*,__isnormal*,__signbit*) and switch infinities to__CPROVER_*inf*. - Update/add regression tests and run
cbmc-libraryregressions on BSD CI.
Reviewed changes
Copilot reviewed 16 out of 16 changed files in this pull request and generated 7 comments.
Show a summary per file
| File | Description |
|---|---|
| src/ansi-c/library_check.sh | Ignores additional platform/builtin math symbols during library checking. |
| src/ansi-c/library/stdio.c | Improves fdopen/_fdopen and standard stream handling; undefines macro-wrapped stdio functions locally. |
| src/ansi-c/library/math.c | Adds/adjusts builtin math entry points and uses __CPROVER_*inf* helpers. |
| src/ansi-c/c_typecheck_expr.cpp | Typechecks additional compiler builtin identifiers (__builtin_is*, __builtin_fabs*). |
| regression/contracts-dfcc/variant_multidimensional_ackermann/main.c | Disables printf call that lacks suitable contracts. |
| regression/cbmc-library/toupper/main.c | Skips test on BSDs pending conversion-table modeling. |
| regression/cbmc-library/tolower/main.c | Skips test on BSDs pending conversion-table modeling. |
| regression/cbmc-library/signbit/main.c | Adds assertions for signbit on double/long double (with Apple guard). |
| regression/cbmc-library/isnormal/main.c | Replaces placeholder failure with a builtin constness check. |
| regression/cbmc-library/isinf/main.c | Adds runtime and compile-time checks for GCC builtins. |
| regression/cbmc-library/isfinite/main.c | Adds behavioral checks comparing isfinite vs fpclassify. |
| regression/cbmc-library/fileno/main.c | Guards fileno assertion when fdopen returns NULL. |
| regression/cbmc-library/copysignl/test.desc | Promotes to CORE and adjusts options. |
| regression/cbmc-library/cnd_broadcast/test.desc | Promotes to CORE and adjusts options. |
| regression/cbmc-library/__fpclassify/main.c | Adds IEEE behavior checks and GCC builtin checks. |
| .github/workflows/bsd.yaml | Runs cbmc-library regressions on BSD jobs (some allowed to fail). |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| # if !defined(__linux__) || defined(__GLIBC__) | ||
| f = malloc(sizeof(FILE)); | ||
| # else | ||
| f = malloc(sizeof(int)); | ||
| # endif | ||
| } | ||
|
|
||
| __CPROVER_assume(fileno(f) == handle); |
There was a problem hiding this comment.
f can be NULL if malloc fails, but fileno(f) is still evaluated in the __CPROVER_assume, which is undefined behavior. Handle allocation failure explicitly (e.g., set errno = ENOMEM; return NULL;) or add an assumption that f is non-NULL before calling fileno.
| return stderr; | ||
| default: | ||
| { | ||
| FILE *f = malloc(sizeof(FILE)); |
There was a problem hiding this comment.
Same issue as the other branch: if malloc returns NULL, fileno(f) is evaluated on a NULL pointer inside __CPROVER_assume. Add an explicit allocation-failure path (set errno = ENOMEM and return NULL) or assume f is non-NULL before calling fileno.
| FILE *f = malloc(sizeof(FILE)); | |
| FILE *f = malloc(sizeof(FILE)); | |
| if(f == NULL) | |
| { | |
| errno = ENOMEM; | |
| return NULL; | |
| } |
| default: | ||
| f = malloc(sizeof(FILE)); | ||
| } | ||
|
|
||
| __CPROVER_assume(fileno(f) == handle); | ||
| return f; |
There was a problem hiding this comment.
_fdopen has the same NULL-allocation hazard: f may be NULL, but fileno(f) is still evaluated. Add explicit handling for allocation failure (e.g., errno = ENOMEM; return NULL;) or assume f is non-NULL before invoking fileno.
| #endif | ||
| assert(signbit(1.0l) == 0); |
There was a problem hiding this comment.
The Apple version guard only applies to the negative long double assertion, but the positive long double assertion remains unguarded. If signbit(long double) is unavailable/incorrect on older Apple targets (as implied by the guard), the 1.0l assertion should likely be inside the same #if (or be guarded by an equivalent condition) to avoid inconsistent behavior across Apple versions.
| #endif | |
| assert(signbit(1.0l) == 0); | |
| assert(signbit(1.0l) == 0); | |
| #endif |
| @@ -1,6 +1,6 @@ | |||
| KNOWNBUG | |||
| CORE | |||
| main.c | |||
There was a problem hiding this comment.
Promoting this test from KNOWNBUG to CORE also removes the previous --pointer-check --bounds-check options. If the intent is to keep the same verification strength while reclassifying the test, consider retaining those options (or documenting in the PR/commit message why they should be dropped).
| main.c | |
| main.c --pointer-check --bounds-check |
| @@ -1,6 +1,6 @@ | |||
| KNOWNBUG | |||
| CORE | |||
There was a problem hiding this comment.
Same as copysignl: switching to CORE also drops --pointer-check --bounds-check. Consider keeping the checks (or documenting why the test should run without them) to avoid unintentionally weakening coverage.
| CORE | |
| CORE --pointer-check --bounds-check |
| # TODO: fileno and *fprintf tests are failing, requires debugging | ||
| # https://github.com/openbsd/src/blob/master/include/stdio.h may be | ||
| # useful (likely need to allocate __sF) | ||
| gmake -C regression/cbmc-library test || true |
There was a problem hiding this comment.
|| true will mask any new regressions in regression/cbmc-library on OpenBSD. Prefer using the workflow’s continue-on-error: true for that step/job, or skipping/xfailing only the known failing tests (e.g., by tagging or filtering), so unrelated failures still surface in CI logs/status.
Fixes portability to FreeBSD, which redefines several functions as macros that would only conditionally call that function. Also, ensure that stdin/stdout/stderr point to valid objects when those are fdopen'ed.