/* SPDX-License-Identifier: MIT OR Apache-2.0 */ /* * The printf oracle for bankml_log (0.3.2): for every case, the message bankml_log delivers to the sink must equal, * byte for byte (length included, an embedded NUL included), what this machine's libc snprintf writes for the same * format and arguments. Then the cases bankml_log does not support: each must come out as its explicit marker, read * no argument it cannot type, and never write through %n. * cc -O2 -Wall -Wextra -Wno-format -Werror printf_oracle.c -I capi/include -L target/release -lbankml * (-Wno-format: some cases are deliberately unusual formats.) Exit 0 only if every case passes. */ #include #include #include #include #include #include #include #include static char got[1 << 16]; static size_t got_len; static int got_level, calls, total, failed; static void sink(int level, const char *msg, size_t len, void *user) { (void)user; if (len >= sizeof got) len = sizeof got - 1; memcpy(got, msg, len); got_len = len; got_level = level; calls++; if (msg[len] != '\0') { printf(" sink: message not NUL-terminated\n"); failed++; } } static void show(const char *what, const char *s, size_t n) { printf(" %s (%zu bytes): \"", what, n); for (size_t i = 0; i < n; i++) { unsigned char c = (unsigned char)s[i]; if (c < 32 || c == '"' || c == '\\' || c >= 127) printf("\\x%02x", c); else putchar(c); } printf("\"\n"); } static void check(const char *label, const char *want, size_t want_len) { total++; if (got_level != BANKML_LOG_INFO || got_len != want_len || memcmp(got, want, want_len) != 0) { failed++; printf(" DIFFERS: %s\n", label); show("libc snprintf", want, want_len); show("bankml_log ", got, got_len); } } /* the same format and arguments through snprintf and bankml_log */ #define SAME(...) \ do { \ static char want_[1 << 16]; \ int n_ = snprintf(want_, sizeof want_, __VA_ARGS__); \ int before_ = calls; \ bankml_log(BANKML_LOG_INFO, __VA_ARGS__); \ if (calls != before_ + 1) { failed++; printf(" no message: %s\n", #__VA_ARGS__); } \ check(#__VA_ARGS__, want_, (size_t)n_); \ } while (0) /* a case bankml_log does not support: the exact marker text */ #define MARKED(want, ...) \ do { \ bankml_log(BANKML_LOG_INFO, __VA_ARGS__); \ check(#__VA_ARGS__, want, strlen(want)); \ } while (0) int main(void) { bankml_set_log(sink, NULL); int local = 0; const char *s = "bankml"; const char unterminated[2] = {'a', 'b'}; /* integers, at their limits and in every supported length */ SAME("%d|%d|%d|%d|%d", 0, 1, -1, INT_MIN, INT_MAX); SAME("%i|%i", INT_MIN, INT_MAX); SAME("%u|%u|%u", 0u, 1u, UINT_MAX); SAME("%ld|%ld|%ld", LONG_MIN, LONG_MAX, 0L); SAME("%lu|%lu", ULONG_MAX, 0UL); SAME("%lld|%lld|%lld", LLONG_MIN, LLONG_MAX, -1LL); SAME("%llu|%llu", ULLONG_MAX, 0ULL); SAME("%zu|%zu|%zd|%zd", SIZE_MAX, (size_t)0, (ssize_t)-1, (ssize_t)PTRDIFF_MAX); SAME("%hhd|%hhd|%hd|%hd|%hhu|%hu", 300, -129, 70000, -32769, 511u, 65537u); SAME("%x|%X|%x|%x|%X", 0xdeadbeefu, 0xdeadbeefu, 0u, UINT_MAX, 0xabcu); SAME("%#x|%#X|%#x", 255u, 255u, 0u); SAME("%lx|%llX|%zx|%hhx|%hx", ULONG_MAX, ULLONG_MAX, SIZE_MAX, 0x1ffu, 0x12345u); SAME("[%5d|%-5d|%05d|%+d|% d|%+ d|%+d]", 42, 42, -42, 7, 7, 7, -7); SAME("[%.3d|%.0d|%.0d|%8.3d|%-8.3d|%08.3d|%.10d]", 5, 0, 1, -5, -5, 5, INT_MIN); SAME("[%08x|%#010x|%-#10x|%#.6x|%.0x|%#.0x]", 0xbeefu, 0xbeefu, 0xbeefu, 0xbeefu, 0u, 0u); SAME("[%*d|%-*d|%*d|%.*d|%.*d|%*.*d]", 6, 42, 6, 42, -6, 42, 4, 7, -1, 7, 8, 5, -3); SAME("[%+u|% u|%+x|% X]", 3u, 3u, 255u, 255u); SAME("[%020lld|%-20llu|%+lld]", LLONG_MIN, ULLONG_MAX, LLONG_MAX); /* %f: precision, rounding (ties to even on the exact binary value), signs, flags, extremes, inf and nan */ SAME("%f|%f|%f|%f", 0.0, -0.0, 1.0, -1.0); SAME("%f|%.3f|%.3f|%.1f", 3.14258, -2.0005, 2.0005, 0.25); SAME("%.0f|%.0f|%.0f|%.0f|%.0f|%.0f", 0.5, 1.5, 2.5, -0.5, 0.49999999999999994, 1e15 + 0.5); SAME("%.20f|%.17f|%.10f|%.30f", 0.1, 0.1 + 0.2, 1.0 / 3.0, 1e-20); SAME("%f|%f", 1e300, -1e300); SAME("%f|%.320f", DBL_MIN, DBL_MIN); SAME("%.1100f", 4.9406564584124654e-324); SAME("%f", DBL_MAX); SAME("[%#.0f|%#f|%#.2f]", 3.0, 3.0, 3.0); SAME("[%10.2f|%-10.2f|%010.2f|%+.1f|% .1f|%+010.3f|% 010.3f]", -1.005, 1.0, -3.25, 0.0, 0.0, 2.5, 2.5); SAME("[%*.*f|%-*.*f|%.*f]", 12, 4, 2.718281828, 12, 4, 2.718281828, -1, 2.718281828); SAME("[%lf|%F|%F]", 2.5, 1.25, -1.25); SAME("[%f|%f|%f|%f]", INFINITY, -INFINITY, NAN, -NAN); SAME("[%F|%F|%F|%F]", INFINITY, -INFINITY, NAN, -NAN); SAME("[%5f|%-6f|%05f|%+f|% f|%010F]", INFINITY, NAN, INFINITY, INFINITY, NAN, -INFINITY); SAME("%.3f|%.3f|%.3f", 1e-4, 9.9995, 0.0005); /* strings */ SAME("[%s|%s|%10s|%-10s|%.3s|%.0s|%.10s]", s, "", s, s, s, s, s); SAME("[%.*s|%*s|%-*s|%*.*s]", 2, s, -9, s, 9, s, 8, 3, s); SAME("[%s|%.5s|%.6s]", "Sav\xc3\xa9 \xf0\x9f\x98\x80", "Sav\xc3\xa9 \xf0\x9f\x98\x80", "Sav\xc3\xa9 \xf0\x9f\x98\x80"); SAME("[%.2s]", unterminated); /* a precision bounds the read: no NUL needed */ SAME("[%s|%.3s|%.6s|%10s]", (char *)NULL, (char *)NULL, (char *)NULL, (char *)NULL); /* characters, an embedded NUL and a high byte included */ SAME("[%c%c|%3c|%-3c|%c|%c]", 'o', 'k', 'x', 'y', 0, 255); /* pointers */ SAME("[%p|%p|%p]", (void *)0x1234, (void *)NULL, (void *)&local); SAME("[%20p|%-20p|%8p|%-8p]", (void *)s, (void *)s, (void *)NULL, (void *)NULL); SAME("[%p]", (void *)UINTPTR_MAX); /* %% and a mix */ SAME("100%%"); SAME("%s=%d (%.2f%%) %c %zu %#x %p end", "turn", -3, 99.5, 'Z', (size_t)4096, 48879u, (void *)0xff); SAME("no conversions at all"); SAME("%s", ""); int supported = total, supported_failed = failed; /* what bankml_log does not support: an explicit marker, never a guess */ MARKED("%", "%e", 2.0); MARKED("1 % % % % end", "%d %g %d %s %% end", 1, 2.0, 3, s); MARKED("%", "%o", 8u); MARKED("%|%", "%a|%d", 1.0, 2); MARKED("%", "%Lf", (long double)1.5); MARKED("%", "%ls", L"wide"); MARKED("%", "%jd", (intmax_t)5); MARKED("%|5", "%+s|%d", s, 5); /* a known type: its argument is read and dropped, the rest goes on */ MARKED("%|6", "%05c|%d", 'q', 6); MARKED("%|7", "%#p|%d", (void *)s, 7); MARKED("%|8", "%#d|%d", 1, 8); MARKED("%|9", "%.3c|%d", 'q', 9); MARKED("%|10", "%70000d|%d", 1, 10); MARKED("%|11", "%5%|%d", 11); MARKED("tail %", "tail %"); int sentinel = 12345; MARKED("before % %", "before %n %d", &sentinel, 1); total++; if (sentinel != 12345) { failed++; printf(" DIFFERS: %%n wrote through its argument\n"); } printf("printf oracle: %d of %d formats byte-identical to libc snprintf (ints at their limits, every length, %%f " "precision and rounding, inf/nan, strings, %%c with NUL, %%p, %%%%, width/precision/*); %d of %d unsupported " "cases written as their marker, no argument guessed, %%n never written\n", supported - supported_failed, supported, (total - supported) - (failed - supported_failed), total - supported); bankml_set_log(NULL, NULL); return failed == 0 ? 0 : 1; }