1 // Copyright 2024, The Android Open Source Project
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14
15 //! This library provides implementation for strtoul libc functions family.
16 //! https://en.cppreference.com/w/cpp/string/byte/strtoul
17
18 use core::ffi::{c_char, c_int, c_ulong, CStr};
19 use safemath::SafeNum;
20
21 /// unsigned long int strtoul(const char *s, char **endptr, int base);
22 ///
23 /// # Safety
24 ///
25 /// * `s` must be valid pointer to null terminated C string
26 /// * `endptr` must be a valid pointer that is available for writing or null
27 #[no_mangle]
strtoul( s: *const c_char, endptr: *mut *const c_char, base: c_int, ) -> c_ulong28 pub unsafe extern "C" fn strtoul(
29 s: *const c_char,
30 endptr: *mut *const c_char,
31 base: c_int,
32 ) -> c_ulong {
33 assert!(!s.is_null());
34 assert!(base == 0 || base == 8 || base == 10 || base == 16);
35
36 let mut pos = 0;
37 let mut base = base;
38 let mut negative = false;
39
40 // SAFETY: `s` is a valid null terminated string
41 let bytes = unsafe { CStr::from_ptr(s) }.to_bytes();
42
43 // Skip leading whitespace
44 while pos < bytes.len() && bytes[pos].is_ascii_whitespace() {
45 pos += 1;
46 }
47
48 // Handle sign
49 if pos < bytes.len() {
50 match bytes[pos] {
51 b'+' => pos += 1,
52 b'-' => {
53 pos += 1;
54 negative = true;
55 }
56 _ => {}
57 }
58 }
59
60 // Handle base prefixes
61 if (base == 16 || base == 0)
62 && pos < bytes.len() - 1
63 && bytes[pos] == b'0'
64 && (bytes[pos + 1] == b'x' || bytes[pos + 1] == b'X')
65 {
66 pos += 2;
67 base = 16;
68 }
69 if (base == 8 || base == 0) && pos < bytes.len() && bytes[pos] == b'0' {
70 pos += 1;
71 base = 8;
72 }
73 if base == 0 {
74 base = 10;
75 }
76
77 let mut result: SafeNum = 0.into();
78 while pos < bytes.len() {
79 let symbol = bytes[pos];
80 let value = match symbol {
81 b'0'..=b'7' if base == 8 => symbol - b'0',
82 b'0'..=b'9' if base == 10 || base == 16 => symbol - b'0',
83 b'a'..=b'f' if base == 16 => symbol - b'a' + 10,
84 b'A'..=b'F' if base == 16 => symbol - b'A' + 10,
85 _ => break,
86 };
87 result = result * base + value;
88 pos += 1;
89 }
90
91 if !endptr.is_null() {
92 // SAFETY: `endptr` is a non-null pointer which is available for writing, `s` is a valid
93 // non-null pointer, `pos` is guaranteed to be within `s` by `pos < bytes.len()` checks.
94 unsafe { *endptr = s.add(pos) };
95 }
96
97 match c_ulong::try_from(result) {
98 Ok(result) if negative => result.overflowing_neg().0,
99 Ok(result) => result,
100 _ => c_ulong::MAX,
101 }
102 }
103
104 #[cfg(test)]
105 mod test {
106 use super::*;
107 use std::ffi::CString;
108 use std::ptr::null_mut;
109
to_cstr(s: &str) -> CString110 fn to_cstr(s: &str) -> CString {
111 CString::new(s).unwrap()
112 }
113
do_strtoul(input: &str, base: i32) -> (c_ulong, Option<usize>)114 fn do_strtoul(input: &str, base: i32) -> (c_ulong, Option<usize>) {
115 let input_cstr = to_cstr(input);
116 let mut end_ptr: *const c_char = null_mut();
117 // SAFETY: `input_cstr` is a null terminated string, `end_ptr` is initialized null pointer
118 let result = unsafe { strtoul(input_cstr.as_ptr(), &mut end_ptr, base) };
119
120 let end_position = if end_ptr.is_null() {
121 None
122 } else {
123 let start_ptr = input_cstr.as_ptr();
124 // SAFETY: `end_ptr` is a pointer within the string that `start_ptr` points to
125 Some(unsafe { end_ptr.offset_from(start_ptr) } as usize)
126 };
127
128 (result, end_position)
129 }
130
do_strtoul_no_endptr(input: &str, base: i32) -> c_ulong131 fn do_strtoul_no_endptr(input: &str, base: i32) -> c_ulong {
132 let input_cstr = to_cstr(input);
133 // SAFETY: `input_cstr` is a null terminated string
134 unsafe { strtoul(input_cstr.as_ptr(), null_mut(), base) }
135 }
136
137 // strtoul tests
138
139 #[test]
strtoul_decimal()140 fn strtoul_decimal() {
141 let (r, end) = do_strtoul("12345", 10);
142 assert_eq!(r, 12345);
143 assert_eq!(end, Some(5));
144 }
145
146 #[test]
strtoul_no_endptr()147 fn strtoul_no_endptr() {
148 let r = do_strtoul_no_endptr("12345", 10);
149 assert_eq!(r, 12345);
150 }
151
152 #[test]
strtoul_zero()153 fn strtoul_zero() {
154 let (r, end) = do_strtoul("0", 10);
155 assert_eq!(r, 0);
156 assert_eq!(end, Some(1));
157 }
158
159 #[test]
strtoul_empty()160 fn strtoul_empty() {
161 let (r, end) = do_strtoul("", 10);
162 assert_eq!(r, 0);
163 // Empty input, end_ptr should point to the start
164 assert_eq!(end, Some(0));
165 }
166
167 #[test]
strtoul_empty_no_endptr()168 fn strtoul_empty_no_endptr() {
169 let r = do_strtoul_no_endptr("", 10);
170 assert_eq!(r, 0);
171 }
172
173 #[test]
strtoul_invalid_characters()174 fn strtoul_invalid_characters() {
175 let (r, end) = do_strtoul("123abc", 10);
176 assert_eq!(r, 123);
177 // Parsing stops at 'a', so end_ptr should point to index 3
178 assert_eq!(end, Some(3));
179 }
180
181 #[test]
strtoul_positive_sign()182 fn strtoul_positive_sign() {
183 let (r, end) = do_strtoul("+456", 10);
184 assert_eq!(r, 456);
185 assert_eq!(end, Some(4));
186 }
187
188 #[test]
strtoul_negative_sign()189 fn strtoul_negative_sign() {
190 let (r, end) = do_strtoul("-1000", 10);
191 assert_eq!(r, 18446744073709550616);
192 assert_eq!(end, Some(5));
193 }
194
195 #[test]
strtoul_negative_zero_sign()196 fn strtoul_negative_zero_sign() {
197 let (r, end) = do_strtoul("-0", 10);
198 assert_eq!(r, 0);
199 assert_eq!(end, Some(2));
200 }
201
202 #[test]
strtoul_prefix_spaces()203 fn strtoul_prefix_spaces() {
204 let (r, end) = do_strtoul(" 456", 10);
205 assert_eq!(r, 456);
206 assert_eq!(end, Some(6));
207 }
208
209 #[test]
strtoul_leading_zeroes()210 fn strtoul_leading_zeroes() {
211 let (r, end) = do_strtoul("0000456", 10);
212 assert_eq!(r, 456);
213 assert_eq!(end, Some(7));
214 }
215
216 #[test]
strtoul_overflow()217 fn strtoul_overflow() {
218 let (r, end) = do_strtoul("999999999999999999999999999999", 10);
219 assert_eq!(r, c_ulong::MAX);
220 // Whole input string got processed, so end_ptr should point to the end
221 assert_eq!(end, Some(30));
222 }
223
224 #[test]
strtoul_octal()225 fn strtoul_octal() {
226 let (r, end) = do_strtoul("12345", 8);
227 assert_eq!(r, 0o12345);
228 assert_eq!(end, Some(5));
229 }
230
231 #[test]
strtoul_octal_prefix()232 fn strtoul_octal_prefix() {
233 let (r, end) = do_strtoul("01234", 8);
234 assert_eq!(r, 0o1234);
235 assert_eq!(end, Some(5));
236 }
237
238 #[test]
strtoul_octal_invalid_characters()239 fn strtoul_octal_invalid_characters() {
240 let (r, end) = do_strtoul("1289", 8);
241 assert_eq!(r, 0o12);
242 assert_eq!(end, Some(2));
243 }
244
245 #[test]
strtoul_octal_prefix_spaces()246 fn strtoul_octal_prefix_spaces() {
247 let (r, end) = do_strtoul(" 0755", 8);
248 assert_eq!(r, 0o755);
249 assert_eq!(end, Some(7));
250 }
251
252 #[test]
strtoul_octal_leading_zeroes()253 fn strtoul_octal_leading_zeroes() {
254 let (r, end) = do_strtoul("0000456", 8);
255 assert_eq!(r, 0o456);
256 assert_eq!(end, Some(7));
257 }
258
259 #[test]
strtoul_octal_overflow()260 fn strtoul_octal_overflow() {
261 let (r, end) = do_strtoul("7777777777777777777777", 8);
262 assert_eq!(r, c_ulong::MAX);
263 assert_eq!(end, Some(22));
264 }
265
266 #[test]
strtoul_hex()267 fn strtoul_hex() {
268 let (r, end) = do_strtoul("12345", 16);
269 assert_eq!(r, 0x12345);
270 assert_eq!(end, Some(5));
271 }
272
273 #[test]
strtoul_hex_prefix()274 fn strtoul_hex_prefix() {
275 let (r, end) = do_strtoul("0x1234", 16);
276 assert_eq!(r, 0x1234);
277 assert_eq!(end, Some(6));
278 }
279
280 #[test]
strtoul_hex_invalid_characters()281 fn strtoul_hex_invalid_characters() {
282 let (r, end) = do_strtoul("12g89", 16);
283 assert_eq!(r, 0x12);
284 assert_eq!(end, Some(2));
285 }
286
287 #[test]
strtoul_hex_prefix_spaces()288 fn strtoul_hex_prefix_spaces() {
289 let (r, end) = do_strtoul(" 0x7F5", 16);
290 assert_eq!(r, 0x7F5);
291 assert_eq!(end, Some(8));
292 }
293
294 #[test]
strtoul_hex_leading_zeroes()295 fn strtoul_hex_leading_zeroes() {
296 let (r, end) = do_strtoul("0000456", 16);
297 assert_eq!(r, 0x456);
298 assert_eq!(end, Some(7));
299 }
300
301 #[test]
strtoul_hex_overflow()302 fn strtoul_hex_overflow() {
303 let (r, end) = do_strtoul("FFFFFFFFFFFFFFFFFFFF", 16);
304 assert_eq!(r, c_ulong::MAX);
305 assert_eq!(end, Some(20));
306 }
307
308 #[test]
strtoul_autodetect_decimal()309 fn strtoul_autodetect_decimal() {
310 let (r, end) = do_strtoul("12345", 0);
311 assert_eq!(r, 12345);
312 assert_eq!(end, Some(5));
313 }
314
315 #[test]
strtoul_autodetect_octal()316 fn strtoul_autodetect_octal() {
317 let (r, end) = do_strtoul("01234", 0);
318 assert_eq!(r, 0o1234);
319 assert_eq!(end, Some(5));
320 }
321
322 #[test]
strtoul_autodetect_hex()323 fn strtoul_autodetect_hex() {
324 let (r, end) = do_strtoul("0x1234", 0);
325 assert_eq!(r, 0x1234);
326 assert_eq!(end, Some(6));
327 }
328
329 #[test]
strtoul_autodetect_hex_invalid()330 fn strtoul_autodetect_hex_invalid() {
331 let (r, end) = do_strtoul("0x12G34", 0);
332 assert_eq!(r, 0x12);
333 assert_eq!(end, Some(4));
334 }
335
336 #[test]
strtoul_autodetect_hex_leading_spaces()337 fn strtoul_autodetect_hex_leading_spaces() {
338 let (r, end) = do_strtoul(" 0x7F5", 0);
339 assert_eq!(r, 0x7F5);
340 assert_eq!(end, Some(8));
341 }
342
343 #[test]
strtoul_autodetect_hex_overflow()344 fn strtoul_autodetect_hex_overflow() {
345 let (r, end) = do_strtoul("0xFFFFFFFFFFFFFFFFFFFF", 0);
346 assert_eq!(r, c_ulong::MAX);
347 assert_eq!(end, Some(22));
348 }
349 }
350