1 /* 2 * Copyright (c) 2026 Faraz Vahedi <kfv@kfv.io> 3 * 4 * SPDX-License-Identifier: BSD-2-Clause 5 */ 6 7 /* 8 * Tests for strfromd(3), strfromf(3), and strfroml(3). 9 * 10 * C23 §7.24.1.3 states that these functions are equivalent to snprintf(3), 11 * albeit with some subtle exceptions, and therefore snprintf(3) is used as 12 * the correctness oracle. Hardcoded string checks guard against both 13 * producing the same wrong answer. 14 */ 15 16 #include <errno.h> 17 #include <float.h> 18 #include <locale.h> 19 #include <math.h> 20 #include <stdio.h> 21 #include <stdlib.h> 22 #include <string.h> 23 24 #include <atf-c.h> 25 26 /* 27 * Buffer large enough for any finite value in %f form, including LDBL_MAX on 28 * platforms with 80-bit or 128-bit long double. Sizing for the full output 29 * keeps the snprintf(3) oracle a full-length comparison rather than letting 30 * both sides truncate alike. 31 */ 32 #define FBUF 8192 33 34 static void 35 check_d(const char *fmt, double val) 36 { 37 char got[FBUF], ref[FBUF]; 38 int rlen, glen; 39 40 rlen = snprintf(ref, sizeof(ref), fmt, val); 41 glen = strfromd(got, sizeof(got), fmt, val); 42 ATF_CHECK_MSG(glen == rlen, 43 "strfromd(\"%s\", %g): return %d, want %d", fmt, val, glen, rlen); 44 ATF_CHECK_STREQ_MSG(ref, got, "strfromd(\"%s\", %g)", fmt, val); 45 } 46 47 static void 48 check_f(const char *fmt, float val) 49 { 50 char got[FBUF], ref[FBUF]; 51 int rlen, glen; 52 53 rlen = snprintf(ref, sizeof(ref), fmt, (double)val); 54 glen = strfromf(got, sizeof(got), fmt, val); 55 ATF_CHECK_MSG(glen == rlen, 56 "strfromf(\"%s\", %g): return %d, want %d", 57 fmt, (double)val, glen, rlen); 58 ATF_CHECK_STREQ_MSG(ref, got, "strfromf(\"%s\", %g)", fmt, (double)val); 59 } 60 61 static void 62 check_l(const char *fmt, long double val) 63 { 64 char got[FBUF], ref[FBUF], lfmt[32]; 65 int rlen, glen; 66 67 /* 68 * snprintf requires the 'L' length modifier for long double. 69 */ 70 snprintf(lfmt, sizeof(lfmt), "%.*sL%c", 71 (int)(strlen(fmt) - 1), fmt, fmt[strlen(fmt) - 1]); 72 rlen = snprintf(ref, sizeof(ref), lfmt, val); 73 glen = strfroml(got, sizeof(got), fmt, val); 74 ATF_CHECK_MSG(glen == rlen, 75 "strfroml(\"%s\", %Lg): return %d, want %d", fmt, val, glen, rlen); 76 ATF_CHECK_STREQ_MSG(ref, got, "strfroml(\"%s\", %Lg)", fmt, val); 77 } 78 79 ATF_TC_WITHOUT_HEAD(strfromd_f); 80 ATF_TC_BODY(strfromd_f, tc) 81 { 82 char buf[FBUF]; 83 84 check_d("%f", 0.0); 85 check_d("%f", 1.0); 86 check_d("%f", -1.0); 87 check_d("%f", 3.14159265358979); 88 check_d("%f", 1.0 / 3.0); 89 check_d("%f", 1e10); 90 check_d("%f", 1e-10); 91 check_d("%f", DBL_MAX); 92 check_d("%f", DBL_MIN); 93 check_d("%.0f", 3.14); 94 check_d("%.0f", 3.5); 95 check_d("%.0f", 4.5); 96 check_d("%.2f", 3.14159); 97 check_d("%.10f", 1.0 / 7.0); 98 check_d("%.20f", 1.0); 99 100 strfromd(buf, sizeof(buf), "%.6f", 0.0); 101 ATF_CHECK_STREQ("0.000000", buf); 102 strfromd(buf, sizeof(buf), "%.6f", -1.5); 103 ATF_CHECK_STREQ("-1.500000", buf); 104 } 105 106 ATF_TC_WITHOUT_HEAD(strfromd_e); 107 ATF_TC_BODY(strfromd_e, tc) 108 { 109 char buf[FBUF]; 110 111 check_d("%e", 0.0); 112 check_d("%e", 1.0); 113 check_d("%e", -1.0); 114 check_d("%e", 3.14159265358979); 115 check_d("%e", 1e100); 116 check_d("%e", 1e-100); 117 check_d("%E", 2.718281828); 118 check_d("%.0e", 3.14); 119 check_d("%.2e", 12345.6789); 120 check_d("%.15e", 1.0 / 3.0); 121 122 strfromd(buf, sizeof(buf), "%e", 1.0); 123 ATF_CHECK_STREQ("1.000000e+00", buf); 124 strfromd(buf, sizeof(buf), "%E", 1.0); 125 ATF_CHECK_STREQ("1.000000E+00", buf); 126 } 127 128 ATF_TC_WITHOUT_HEAD(strfromd_g); 129 ATF_TC_BODY(strfromd_g, tc) 130 { 131 char buf[FBUF]; 132 133 check_d("%g", 0.0); 134 check_d("%g", 1.0); 135 check_d("%g", 100000.0); 136 check_d("%g", 1000000.0); /* exponent >= prec: hence %e */ 137 check_d("%g", 0.0001); /* exponent == -4: hence %f */ 138 check_d("%g", 0.00001); /* exponent < -4: hence %e */ 139 check_d("%g", 3.14159265358979); 140 check_d("%G", 1.23e10); 141 check_d("%.2g", 3.14159); 142 check_d("%.0g", 3.14159); 143 check_d("%.10g", 1.0 / 7.0); 144 check_d("%g", DBL_MIN); 145 check_d("%g", DBL_MAX); 146 147 strfromd(buf, sizeof(buf), "%g", 1.0); 148 ATF_CHECK_STREQ("1", buf); 149 strfromd(buf, sizeof(buf), "%g", 100.0); 150 ATF_CHECK_STREQ("100", buf); 151 } 152 153 ATF_TC_WITHOUT_HEAD(strfromd_a); 154 ATF_TC_BODY(strfromd_a, tc) 155 { 156 char buf[FBUF]; 157 158 check_d("%a", 0.0); 159 check_d("%a", 1.0); 160 check_d("%a", -1.0); 161 check_d("%a", 0.5); 162 check_d("%a", 1.5); 163 check_d("%a", DBL_MIN); 164 check_d("%a", DBL_MAX); 165 check_d("%A", 1.0); 166 check_d("%.4a", 1.0 / 3.0); 167 check_d("%.0a", 1.5); 168 169 strfromd(buf, sizeof(buf), "%a", 1.0); 170 ATF_CHECK_STREQ("0x1p+0", buf); 171 strfromd(buf, sizeof(buf), "%a", 0.5); 172 ATF_CHECK_STREQ("0x1p-1", buf); 173 strfromd(buf, sizeof(buf), "%A", 1.0); 174 ATF_CHECK_STREQ("0X1P+0", buf); 175 } 176 177 ATF_TC_WITHOUT_HEAD(strfromd_special); 178 ATF_TC_BODY(strfromd_special, tc) 179 { 180 char buf[FBUF]; 181 182 check_d("%f", INFINITY); 183 check_d("%f", -INFINITY); 184 check_d("%f", NAN); 185 check_d("%e", INFINITY); 186 check_d("%e", -INFINITY); 187 check_d("%e", NAN); 188 check_d("%g", INFINITY); 189 check_d("%g", -INFINITY); 190 check_d("%g", NAN); 191 check_d("%a", INFINITY); 192 check_d("%a", -INFINITY); 193 check_d("%a", NAN); 194 check_d("%F", INFINITY); 195 check_d("%E", INFINITY); 196 check_d("%G", INFINITY); 197 check_d("%A", INFINITY); 198 check_d("%f", -0.0); 199 check_d("%g", -0.0); 200 check_d("%e", -0.0); 201 check_d("%a", -0.0); 202 203 strfromd(buf, sizeof(buf), "%f", INFINITY); 204 ATF_CHECK_STREQ("inf", buf); 205 strfromd(buf, sizeof(buf), "%F", INFINITY); 206 ATF_CHECK_STREQ("INF", buf); 207 strfromd(buf, sizeof(buf), "%f", -INFINITY); 208 ATF_CHECK_STREQ("-inf", buf); 209 strfromd(buf, sizeof(buf), "%f", NAN); 210 ATF_CHECK_STREQ("nan", buf); 211 strfromd(buf, sizeof(buf), "%F", NAN); 212 ATF_CHECK_STREQ("NAN", buf); 213 } 214 215 ATF_TC_WITHOUT_HEAD(strfromd_truncation); 216 ATF_TC_BODY(strfromd_truncation, tc) 217 { 218 char buf[FBUF]; 219 int ret; 220 221 /* 222 * When n == 0 the buffer must not be touched and the return value 223 * must still reflect the full output length. 224 */ 225 buf[0] = 'X'; 226 ret = strfromd(buf, 0, "%f", 1.0); 227 ATF_CHECK(ret > 0); 228 ATF_CHECK_MSG(buf[0] == 'X', "buffer modified with n==0"); 229 230 /* 231 * With a short buffer, a null terminated prefix is written and the 232 * return value is the length that would have been needed. 233 */ 234 ret = strfromd(buf, 5, "%.6f", 1.0); 235 ATF_CHECK_MSG(ret == 8, "return %d, want 8", ret); 236 ATF_CHECK_MSG(buf[4] == '\0', "not null terminated at buf[4]"); 237 ATF_CHECK_MSG(strncmp(buf, "1.00", 4) == 0, 238 "prefix mismatch: got \"%.*s\"", 4, buf); 239 } 240 241 ATF_TC_WITHOUT_HEAD(strfromf_f); 242 ATF_TC_BODY(strfromf_f, tc) 243 { 244 check_f("%f", 0.0f); 245 check_f("%f", 1.0f); 246 check_f("%f", -1.0f); 247 check_f("%f", 3.14159f); 248 check_f("%.0f", 3.5f); 249 check_f("%.2f", 3.14159f); 250 check_f("%.9f", 1.0f / 3.0f); 251 check_f("%f", FLT_MAX); 252 check_f("%f", FLT_MIN); 253 } 254 255 ATF_TC_WITHOUT_HEAD(strfromf_e); 256 ATF_TC_BODY(strfromf_e, tc) 257 { 258 char buf[FBUF]; 259 260 check_f("%e", 0.0f); 261 check_f("%e", 1.0f); 262 check_f("%e", -1.0f); 263 check_f("%e", 3.14159f); 264 check_f("%E", 2.71828f); 265 check_f("%.2e", 12345.6f); 266 check_f("%.0e", 3.14f); 267 268 strfromf(buf, sizeof(buf), "%e", 1.0f); 269 ATF_CHECK_STREQ("1.000000e+00", buf); 270 } 271 272 ATF_TC_WITHOUT_HEAD(strfromf_g); 273 ATF_TC_BODY(strfromf_g, tc) 274 { 275 char buf[FBUF]; 276 277 check_f("%g", 0.0f); 278 check_f("%g", 1.0f); 279 check_f("%g", 100000.0f); 280 check_f("%g", 1000000.0f); 281 check_f("%g", 0.0001f); 282 check_f("%g", 0.00001f); 283 check_f("%G", 1.23e10f); 284 check_f("%.2g", 3.14159f); 285 286 strfromf(buf, sizeof(buf), "%g", 1.0f); 287 ATF_CHECK_STREQ("1", buf); 288 } 289 290 ATF_TC_WITHOUT_HEAD(strfromf_a); 291 ATF_TC_BODY(strfromf_a, tc) 292 { 293 char buf[FBUF]; 294 295 check_f("%a", 0.0f); 296 check_f("%a", 1.0f); 297 check_f("%a", 0.5f); 298 check_f("%a", -1.0f); 299 check_f("%A", 1.0f); 300 check_f("%.2a", 1.0f / 3.0f); 301 302 strfromf(buf, sizeof(buf), "%a", 1.0f); 303 ATF_CHECK_STREQ("0x1p+0", buf); 304 } 305 306 ATF_TC_WITHOUT_HEAD(strfromf_special); 307 ATF_TC_BODY(strfromf_special, tc) 308 { 309 check_f("%f", INFINITY); 310 check_f("%f", -INFINITY); 311 check_f("%f", NAN); 312 check_f("%e", INFINITY); 313 check_f("%g", INFINITY); 314 check_f("%a", INFINITY); 315 check_f("%F", INFINITY); 316 check_f("%f", -0.0f); 317 check_f("%g", -0.0f); 318 } 319 320 ATF_TC_WITHOUT_HEAD(strfromf_truncation); 321 ATF_TC_BODY(strfromf_truncation, tc) 322 { 323 char buf[FBUF]; 324 int ret; 325 326 buf[0] = 'X'; 327 ret = strfromf(buf, 0, "%f", 1.0f); 328 ATF_CHECK(ret > 0); 329 ATF_CHECK_MSG(buf[0] == 'X', "buffer modified with n==0"); 330 331 ret = strfromf(buf, 5, "%.6f", 1.0f); 332 ATF_CHECK_MSG(ret == 8, "return %d, want 8", ret); 333 ATF_CHECK_MSG(buf[4] == '\0', "not null terminated at buf[4]"); 334 } 335 336 ATF_TC_WITHOUT_HEAD(strfroml_f); 337 ATF_TC_BODY(strfroml_f, tc) 338 { 339 check_l("%f", 0.0L); 340 check_l("%f", 1.0L); 341 check_l("%f", -1.0L); 342 check_l("%f", 3.14159265358979323846L); 343 check_l("%.0f", 3.5L); 344 check_l("%.2f", 3.14159L); 345 check_l("%.15f", 1.0L / 3.0L); 346 check_l("%f", LDBL_MAX); 347 check_l("%f", LDBL_MIN); 348 } 349 350 ATF_TC_WITHOUT_HEAD(strfroml_e); 351 ATF_TC_BODY(strfroml_e, tc) 352 { 353 char buf[FBUF]; 354 355 check_l("%e", 0.0L); 356 check_l("%e", 1.0L); 357 check_l("%e", -1.0L); 358 check_l("%e", 3.14159265358979323846L); 359 check_l("%E", 2.71828182845904523536L); 360 check_l("%.2e", 12345.6789L); 361 check_l("%.0e", 3.14L); 362 check_l("%e", LDBL_MAX); 363 check_l("%e", LDBL_MIN); 364 365 strfroml(buf, sizeof(buf), "%e", 1.0L); 366 ATF_CHECK_STREQ("1.000000e+00", buf); 367 } 368 369 ATF_TC_WITHOUT_HEAD(strfroml_g); 370 ATF_TC_BODY(strfroml_g, tc) 371 { 372 char buf[FBUF]; 373 374 check_l("%g", 0.0L); 375 check_l("%g", 1.0L); 376 check_l("%g", 100000.0L); 377 check_l("%g", 1000000.0L); 378 check_l("%g", 0.0001L); 379 check_l("%g", 0.00001L); 380 check_l("%G", 1.23e10L); 381 check_l("%.2g", 3.14159L); 382 check_l("%.0g", 3.14159L); 383 384 strfroml(buf, sizeof(buf), "%g", 1.0L); 385 ATF_CHECK_STREQ("1", buf); 386 } 387 388 ATF_TC_WITHOUT_HEAD(strfroml_a); 389 ATF_TC_BODY(strfroml_a, tc) 390 { 391 char buf[FBUF]; 392 393 check_l("%a", 0.0L); 394 check_l("%a", 1.0L); 395 check_l("%a", 0.5L); 396 check_l("%a", -1.0L); 397 check_l("%A", 1.0L); 398 check_l("%.4a", 1.0L / 3.0L); 399 check_l("%.0a", 1.5L); 400 check_l("%a", LDBL_MAX); 401 check_l("%a", LDBL_MIN); 402 403 strfroml(buf, sizeof(buf), "%a", 1.0L); 404 ATF_CHECK_STREQ("0x1p+0", buf); 405 } 406 407 ATF_TC_WITHOUT_HEAD(strfroml_special); 408 ATF_TC_BODY(strfroml_special, tc) 409 { 410 check_l("%f", INFINITY); 411 check_l("%f", -INFINITY); 412 check_l("%f", NAN); 413 check_l("%e", INFINITY); 414 check_l("%g", INFINITY); 415 check_l("%a", INFINITY); 416 check_l("%F", INFINITY); 417 check_l("%f", -0.0L); 418 check_l("%g", -0.0L); 419 #ifdef __HAVE_LONG_DOUBLE 420 check_l("%.20f", 1.0L / 3.0L); 421 check_l("%.20e", 1.0L / 7.0L); 422 #endif 423 } 424 425 ATF_TC_WITHOUT_HEAD(strfroml_truncation); 426 ATF_TC_BODY(strfroml_truncation, tc) 427 { 428 char buf[FBUF]; 429 int ret; 430 431 buf[0] = 'X'; 432 ret = strfroml(buf, 0, "%f", 1.0L); 433 ATF_CHECK(ret > 0); 434 ATF_CHECK_MSG(buf[0] == 'X', "buffer modified with n==0"); 435 436 ret = strfroml(buf, 5, "%.6f", 1.0L); 437 ATF_CHECK_MSG(ret == 8, "return %d, want 8", ret); 438 ATF_CHECK_MSG(buf[4] == '\0', "not null terminated at buf[4]"); 439 } 440 441 ATF_TC_WITHOUT_HEAD(strfrom_locale); 442 ATF_TC_BODY(strfrom_locale, tc) 443 { 444 char buf[FBUF]; 445 446 /* 447 * strfrom* is equivalent to snprintf(3) and so honours LC_NUMERIC: 448 * In a locale whose radix is not '.', the output must use that radix. 449 * The check_* helpers already compare against snprintf in this locale; 450 * the literal checks confirm the radix actually changed for both the 451 * decimal and hexadecimal paths. Skip if the locale is unavailable. 452 */ 453 if (setlocale(LC_NUMERIC, "de_DE.UTF-8") == NULL) 454 atf_tc_skip("de_DE.UTF-8 locale not available"); 455 456 check_d("%f", 1.5); 457 check_d("%e", 1.5); 458 check_d("%g", 1.5); 459 check_d("%a", 1.5); 460 check_f("%f", 1.5f); 461 check_l("%f", 1.5L); 462 463 strfromd(buf, sizeof(buf), "%.1f", 1.5); 464 ATF_CHECK_STREQ("1,5", buf); 465 strfromd(buf, sizeof(buf), "%a", 1.5); 466 ATF_CHECK_STREQ("0x1,8p+0", buf); 467 } 468 469 /* 470 * Per C23 §7.24.1.3p2, format string outside the restricted grammar is 471 * undefined behaviour. FreeBSD reports it as the programming error it 472 * is; the conversion yields "EDOOFUS" and sets errno accordingly. 473 */ 474 static void 475 check_edoofus(const char *fmt, int ret, const char *buf) 476 { 477 ATF_CHECK_MSG(ret == 7, "%s: return %d, want 7", fmt, ret); 478 ATF_CHECK_STREQ_MSG("EDOOFUS", buf, "%s: got \"%s\"", fmt, buf); 479 ATF_CHECK_MSG(errno == EDOOFUS, "%s: errno %d, want %d", fmt, errno, 480 EDOOFUS); 481 } 482 483 ATF_TC_WITHOUT_HEAD(strfrom_empty_format); 484 ATF_TC_BODY(strfrom_empty_format, tc) 485 { 486 char buf[FBUF]; 487 int ret; 488 489 errno = 0; 490 ret = strfromd(buf, sizeof(buf), "", 1.0); 491 check_edoofus("", ret, buf); 492 } 493 494 ATF_TC_WITHOUT_HEAD(strfrom_bad_specifier); 495 ATF_TC_BODY(strfrom_bad_specifier, tc) 496 { 497 char buf[FBUF]; 498 int ret; 499 500 errno = 0; 501 ret = strfromd(buf, sizeof(buf), "%q", 1.0); 502 check_edoofus("%q", ret, buf); 503 504 errno = 0; 505 ret = strfromf(buf, sizeof(buf), "%.2q", 1.0f); 506 check_edoofus("%.2q", ret, buf); 507 508 errno = 0; 509 ret = strfroml(buf, sizeof(buf), "%", 1.0L); 510 check_edoofus("%", ret, buf); 511 512 errno = 0; 513 ret = strfromd(buf, sizeof(buf), "f", 1.0); 514 check_edoofus("f", ret, buf); 515 516 errno = 0; 517 ret = strfromd(buf, sizeof(buf), "%*f", 1.0); 518 check_edoofus("%*f", ret, buf); 519 520 errno = 0; 521 ret = strfromd(buf, sizeof(buf), "%f ", 1.0); 522 check_edoofus("%f ", ret, buf); 523 } 524 525 ATF_TC_WITHOUT_HEAD(strfrom_bad_format_truncation); 526 ATF_TC_BODY(strfrom_bad_format_truncation, tc) 527 { 528 char buf[FBUF]; 529 int ret; 530 531 /* The error string obeys the snprintf(3) truncation rules. */ 532 buf[0] = 'X'; 533 errno = 0; 534 ret = strfromd(buf, 0, "%q", 1.0); 535 ATF_CHECK_MSG(ret == 7, "return %d, want 7", ret); 536 ATF_CHECK_MSG(buf[0] == 'X', "buffer modified with n==0"); 537 ATF_CHECK_EQ(EDOOFUS, errno); 538 539 errno = 0; 540 ret = strfromd(buf, 4, "%q", 1.0); 541 ATF_CHECK_MSG(ret == 7, "return %d, want 7", ret); 542 ATF_CHECK_STREQ("EDO", buf); 543 ATF_CHECK_EQ(EDOOFUS, errno); 544 } 545 546 ATF_TC_WITHOUT_HEAD(strfrom_errno_preserved); 547 ATF_TC_BODY(strfrom_errno_preserved, tc) 548 { 549 char buf[FBUF]; 550 551 errno = 0; 552 strfromd(buf, sizeof(buf), "%f", 1.0); 553 ATF_CHECK_EQ(0, errno); 554 strfromf(buf, sizeof(buf), "%a", 1.0f); 555 ATF_CHECK_EQ(0, errno); 556 strfroml(buf, sizeof(buf), "%.3g", 1.0L); 557 ATF_CHECK_EQ(0, errno); 558 } 559 560 ATF_TP_ADD_TCS(tp) 561 { 562 ATF_TP_ADD_TC(tp, strfromd_f); 563 ATF_TP_ADD_TC(tp, strfromd_e); 564 ATF_TP_ADD_TC(tp, strfromd_g); 565 ATF_TP_ADD_TC(tp, strfromd_a); 566 ATF_TP_ADD_TC(tp, strfromd_special); 567 ATF_TP_ADD_TC(tp, strfromd_truncation); 568 569 ATF_TP_ADD_TC(tp, strfromf_f); 570 ATF_TP_ADD_TC(tp, strfromf_e); 571 ATF_TP_ADD_TC(tp, strfromf_g); 572 ATF_TP_ADD_TC(tp, strfromf_a); 573 ATF_TP_ADD_TC(tp, strfromf_special); 574 ATF_TP_ADD_TC(tp, strfromf_truncation); 575 576 ATF_TP_ADD_TC(tp, strfroml_f); 577 ATF_TP_ADD_TC(tp, strfroml_e); 578 ATF_TP_ADD_TC(tp, strfroml_g); 579 ATF_TP_ADD_TC(tp, strfroml_a); 580 ATF_TP_ADD_TC(tp, strfroml_special); 581 ATF_TP_ADD_TC(tp, strfroml_truncation); 582 583 ATF_TP_ADD_TC(tp, strfrom_locale); 584 ATF_TP_ADD_TC(tp, strfrom_empty_format); 585 ATF_TP_ADD_TC(tp, strfrom_bad_specifier); 586 ATF_TP_ADD_TC(tp, strfrom_bad_format_truncation); 587 ATF_TP_ADD_TC(tp, strfrom_errno_preserved); 588 589 return (atf_no_error()); 590 } 591