File size: 7,947 Bytes
28c70af
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
/* 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 <bankml.h>
#include <float.h>
#include <limits.h>
#include <math.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <sys/types.h>

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("%<unsupported:e>", "%e", 2.0);
  MARKED("1 %<unsupported:g> %<skipped:d> %<skipped:s> % end", "%d %g %d %s %% end", 1, 2.0, 3, s);
  MARKED("%<unsupported:o>", "%o", 8u);
  MARKED("%<unsupported:a>|%<skipped:d>", "%a|%d", 1.0, 2);
  MARKED("%<unsupported:Lf>", "%Lf", (long double)1.5);
  MARKED("%<unsupported:ls>", "%ls", L"wide");
  MARKED("%<unsupported:jd>", "%jd", (intmax_t)5);
  MARKED("%<unsupported:+s>|5", "%+s|%d", s, 5);   /* a known type: its argument is read and dropped, the rest goes on */
  MARKED("%<unsupported:05c>|6", "%05c|%d", 'q', 6);
  MARKED("%<unsupported:#p>|7", "%#p|%d", (void *)s, 7);
  MARKED("%<unsupported:#d>|8", "%#d|%d", 1, 8);
  MARKED("%<unsupported:.3c>|9", "%.3c|%d", 'q', 9);
  MARKED("%<unsupported:70000d>|10", "%70000d|%d", 1, 10);
  MARKED("%<unsupported:5%>|11", "%5%|%d", 11);
  MARKED("tail %<incomplete:>", "tail %");
  int sentinel = 12345;
  MARKED("before %<unsupported:n> %<skipped:d>", "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;
}