xref: /illumos-gate/usr/src/test/libc-tests/tests/strtonum.c (revision e8d3efa1c56e5f2b5368600a2baeb7b1d54a07f8)
1 /*
2  * This file and its contents are supplied under the terms of the
3  * Common Development and Distribution License ("CDDL"), version 1.0.
4  * You may only use this file in accordance with the terms of version
5  * 1.0 of the CDDL.
6  *
7  * A full copy of the text of the CDDL should have accompanied this
8  * source.  A copy of the CDDL is also available via the Internet at
9  * http://www.illumos.org/license/CDDL.
10  */
11 
12 /*
13  * Copyright 2026 Oxide Computer Company
14  */
15 
16 /*
17  * Test the implementation of strtonum(), strtonumx(), strtounum() and
18  * strtounumx()
19  */
20 
21 #include <errno.h>
22 #include <limits.h>
23 #include <stdbool.h>
24 #include <stdio.h>
25 #include <stdlib.h>
26 #include <string.h>
27 
28 bool fail = false;
29 
30 static void
31 runtestx(const char *nstr, long long minval, long long maxval,
32     int base, long long expval, bool expect_pass)
33 {
34 	const char *errstr;
35 	char key[0x100];
36 	long long val;
37 	int n;
38 
39 	if (base == -1) {
40 		val = strtonum(nstr, minval, maxval, &errstr);
41 		n = snprintf(key, sizeof (key), "strtonum(%s)", nstr);
42 	} else {
43 		val = strtonumx(nstr, minval, maxval, &errstr, base);
44 		n = snprintf(key, sizeof (key), "strtonumx(%s, %d)",
45 		    nstr, base);
46 	}
47 	if (n < 0 || (size_t)n >= sizeof (key)) {
48 		fprintf(stderr, "FAIL could not build key for '%s'\n", nstr);
49 		fail = true;
50 		return;
51 	}
52 
53 	if (expect_pass) {
54 		if (errstr != NULL) {
55 			fprintf(stderr, "FAIL %s was rejected: "
56 			    "%s [%lld,%lld]\n",
57 			    key, errstr, minval, maxval);
58 			fail = true;
59 		} else {
60 			fprintf(stdout, "PASS %s was accepted: "
61 			    "[%lld,%lld] = %lld\n",
62 			    key, minval, maxval, val);
63 			if (val != expval) {
64 				fprintf(stderr, "FAIL %s returned wrong value: "
65 				    "[%lld,%lld] = %lld vs. %lld\n", key,
66 				    minval, maxval, val, expval);
67 				fail = true;
68 			}
69 		}
70 	} else {
71 		if (errstr == NULL) {
72 			fprintf(stderr, "FAIL %s was accepted: "
73 			    "[%lld,%lld] = %lld\n",
74 			    key, minval, maxval, val);
75 			fail = true;
76 		} else {
77 			fprintf(stdout, "PASS %s was rejected: "
78 			    "%s [%lld,%lld]\n",
79 			    key, errstr, minval, maxval);
80 		}
81 	}
82 }
83 
84 static void
85 runtest(const char *nstr, long long minval, long long maxval, long long expval,
86     bool expect_pass)
87 {
88 	runtestx(nstr, minval, maxval, -1, expval, expect_pass);
89 	runtestx(nstr, minval, maxval, 10, expval, expect_pass);
90 	runtestx(nstr, minval, maxval,  0, expval, expect_pass);
91 }
92 
93 static void
94 runtestux(const char *nstr, unsigned long long minval,
95     unsigned long long maxval, int base, unsigned long long expval,
96     bool expect_pass, const char *experr)
97 {
98 	const char *errstr;
99 	char key[0x100];
100 	unsigned long long val;
101 	int n;
102 
103 	if (base == -1) {
104 		val = strtounum(nstr, minval, maxval, &errstr);
105 		n = snprintf(key, sizeof (key), "strtounum(%s)", nstr);
106 	} else {
107 		val = strtounumx(nstr, minval, maxval, &errstr, base);
108 		n = snprintf(key, sizeof (key), "strtounumx(%s, %d)",
109 		    nstr, base);
110 	}
111 	if (n < 0 || (size_t)n >= sizeof (key)) {
112 		fprintf(stderr, "FAIL could not build key for '%s'\n", nstr);
113 		fail = true;
114 		return;
115 	}
116 
117 	if (expect_pass) {
118 		if (errstr != NULL) {
119 			fprintf(stderr, "FAIL %s was rejected: "
120 			    "%s [%llu,%llu]\n",
121 			    key, errstr, minval, maxval);
122 			fail = true;
123 		} else {
124 			fprintf(stdout, "PASS %s was accepted: "
125 			    "[%llu,%llu] = %llu\n",
126 			    key, minval, maxval, val);
127 			if (val != expval) {
128 				fprintf(stderr, "FAIL %s returned wrong value: "
129 				    "[%llu,%llu] = %llu vs. %llu\n", key,
130 				    minval, maxval, val, expval);
131 				fail = true;
132 			}
133 		}
134 	} else {
135 		if (errstr == NULL) {
136 			fprintf(stderr, "FAIL %s was accepted: "
137 			    "[%llu,%llu] = %llu\n",
138 			    key, minval, maxval, val);
139 			fail = true;
140 		} else if (experr != NULL && strcmp(errstr, experr) != 0) {
141 			fprintf(stderr, "FAIL %s wrong error: "
142 			    "'%s' vs. '%s' [%llu,%llu]\n",
143 			    key, errstr, experr, minval, maxval);
144 			fail = true;
145 		} else {
146 			fprintf(stdout, "PASS %s was rejected: "
147 			    "%s [%llu,%llu]\n",
148 			    key, errstr, minval, maxval);
149 		}
150 	}
151 }
152 
153 static void
154 runtestu(const char *nstr, unsigned long long minval,
155     unsigned long long maxval, unsigned long long expval, bool expect_pass,
156     const char *experr)
157 {
158 	runtestux(nstr, minval, maxval, -1, expval, expect_pass, experr);
159 	runtestux(nstr, minval, maxval, 10, expval, expect_pass, experr);
160 	runtestux(nstr, minval, maxval,  0, expval, expect_pass, experr);
161 }
162 
163 /*
164  * The unsigned functions must restore the caller's errno on success and
165  * set it appropriately on failure.
166  */
167 static void
168 runtest_errno(void)
169 {
170 	const char *errstr;
171 	unsigned long long val;
172 
173 	errno = EDOM;
174 	val = strtounum("5", 0, 10, &errstr);
175 	if (errstr != NULL || errno != EDOM || val != 5) {
176 		fprintf(stderr, "FAIL errno/value wrong on success: "
177 		    "errno %d vs. %d, value %llu vs. 5\n", errno, EDOM, val);
178 		fail = true;
179 	} else {
180 		fprintf(stdout, "PASS errno preserved on success\n");
181 	}
182 
183 	errno = 0;
184 	val = strtounum("11", 0, 10, &errstr);
185 	if (errstr == NULL || errno != ERANGE || val != 0) {
186 		fprintf(stderr, "FAIL errno/value wrong on range error: "
187 		    "errno %d, value %llu\n", errno, val);
188 		fail = true;
189 	} else {
190 		fprintf(stdout, "PASS errno set to ERANGE on range error\n");
191 	}
192 
193 	errno = 0;
194 	val = strtounum("zebra", 0, 10, &errstr);
195 	if (errstr == NULL || errno != EINVAL || val != 0) {
196 		fprintf(stderr, "FAIL errno/value wrong on invalid input: "
197 		    "errno %d, value %llu\n", errno, val);
198 		fail = true;
199 	} else {
200 		fprintf(stdout, "PASS errno set to EINVAL on invalid input\n");
201 	}
202 }
203 
204 int
205 main(void)
206 {
207 	runtest("1", 0, 100, 1, true);
208 	runtest("0", -3, 7, 0, true);
209 	runtest("0", 2, 10, 0, false);
210 	runtest("0", 2, LLONG_MAX, 0, false);
211 	runtest("-2", 0, LLONG_MAX, 0, false);
212 	runtest("0", -5, LLONG_MAX, 0, true);
213 	runtest("-3", -3, LLONG_MAX, -3, true);
214 	runtest("-2", 10, -1, 0, false);
215 	runtest("-2", -10, -1, -2, true);
216 	runtest("-20", -10, -1, 0, false);
217 	runtest("20", -10, -1, 0, false);
218 
219 	runtest("-9223372036854775808", LLONG_MIN, LLONG_MAX, LLONG_MIN, true);
220 	runtest("-9223372036854775809", LLONG_MIN, LLONG_MAX, 0, false);
221 
222 	runtest("9223372036854775807", LLONG_MIN, LLONG_MAX, LLONG_MAX, true);
223 	runtest("9223372036854775808", LLONG_MIN, LLONG_MAX, 0, false);
224 
225 	for (int base = 2; base <= 36; base++)
226 		runtestx("1", 0, 100, base, 1, true);
227 
228 	runtestx("1", 0, 100, -2, 0, false);
229 	runtestx("1", 0, 100, 1, 0, false);
230 	runtestx("1", 0, 100, 37, 0, false);
231 
232 	runtestx("0x1234", 0, LLONG_MAX, 16, 0x1234, true);
233 	runtestx("0x1234", 0, LLONG_MAX, 0, 0x1234, true);
234 	runtestx("0x1234", 0, LLONG_MAX, 10, 0, false);
235 
236 	runtestx("AZbc123", 0, LLONG_MAX, 36, 23903176539, true);
237 	runtestx("AZbc123", 0, LLONG_MAX, 35, 0, false);
238 	runtestx("AYBC123", 0, LLONG_MAX, 35, 20185422673, true);
239 	runtestx("AYBC123", 0, LLONG_MAX, 34, 0, false);
240 
241 	runtestx("01234", 0, LLONG_MAX, 8, 01234, true);
242 	runtestx("01234", 0, LLONG_MAX, 0, 01234, true);
243 	runtestx("01234", 0, LLONG_MAX, 10, 1234, true);
244 
245 	/* Unsigned variants */
246 	runtestu("1", 0, 100, 1, true, NULL);
247 	runtestu("0", 0, 100, 0, true, NULL);
248 	runtestu("+42", 0, 100, 42, true, NULL);
249 	runtestu("0", 2, 10, 0, false, "too small");
250 	runtestu("11", 0, 10, 0, false, "too large");
251 	runtestu("5", 10, 2, 0, false, "invalid");
252 	runtestu("", 0, 100, 0, false, "invalid");
253 	runtestu("zebra", 0, 100, 0, false, "invalid");
254 
255 	/* Values beyond the signed 64-bit range must work */
256 	runtestu("9223372036854775808", 0, ULLONG_MAX,
257 	    9223372036854775808ULL, true, NULL);
258 	runtestu("18446744073709551615", 0, ULLONG_MAX, ULLONG_MAX, true,
259 	    NULL);
260 	runtestu("18446744073709551616", 0, ULLONG_MAX, 0, false,
261 	    "too large");
262 	runtestu("18446744073709551615", 0, 100, 0, false, "too large");
263 
264 	/* Negative input is always rejected, never wrapped */
265 	runtestu("-1", 0, ULLONG_MAX, 0, false, "too small");
266 	runtestu("-0", 0, ULLONG_MAX, 0, false, "too small");
267 	runtestu("  -5", 0, ULLONG_MAX, 0, false, "too small");
268 	runtestu("-18446744073709551615", 0, ULLONG_MAX, 0, false,
269 	    "too small");
270 
271 	/* Malformed minus-prefixed input is invalid, not out of range */
272 	runtestu("-", 0, ULLONG_MAX, 0, false, "invalid");
273 	runtestu("  -", 0, ULLONG_MAX, 0, false, "invalid");
274 	runtestu("-xyz", 0, ULLONG_MAX, 0, false, "invalid");
275 	runtestu("- 5", 0, ULLONG_MAX, 0, false, "invalid");
276 	runtestu("--5", 0, ULLONG_MAX, 0, false, "invalid");
277 	runtestu("-5x", 0, ULLONG_MAX, 0, false, "invalid");
278 
279 	/* Base handling */
280 	for (int base = 2; base <= 36; base++)
281 		runtestux("1", 0, 100, base, 1, true, NULL);
282 
283 	runtestux("1", 0, 100, -2, 0, false, NULL);
284 	runtestux("1", 0, 100, 1, 0, false, NULL);
285 	runtestux("1", 0, 100, 37, 0, false, NULL);
286 
287 	runtestux("0x20000000", 0, ULLONG_MAX, 0, 0x20000000ULL, true, NULL);
288 	runtestux("0x20000000", 0, ULLONG_MAX, 16, 0x20000000ULL, true, NULL);
289 	runtestux("0xffffffffffffffff", 0, ULLONG_MAX, 16, ULLONG_MAX, true,
290 	    NULL);
291 	runtestux("0x1234", 0, ULLONG_MAX, 10, 0, false, "invalid");
292 	runtestux("01234", 0, ULLONG_MAX, 0, 01234, true, NULL);
293 
294 	runtest_errno();
295 
296 	if (fail) {
297 		printf("\nOverall status: FAIL\n");
298 		return (EXIT_FAILURE);
299 	}
300 
301 	printf("\nOverall status: PASS\n");
302 	return (EXIT_SUCCESS);
303 }
304