xref: /aosp_15_r20/bootable/libbootloader/gbl/libc/src/strtoul.rs (revision 5225e6b173e52d2efc6bcf950c27374fd72adabc)
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