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 strchr libc functions family.
16 //! https://en.cppreference.com/w/c/string/byte/strchr
17
18 use core::ffi::{c_char, c_int, CStr};
19 use core::ptr::null_mut;
20
21 /// char *strchr(const char *str, int c);
22 ///
23 /// # Safety
24 ///
25 /// * `str` must be a valid null-terminated C string.
26 #[no_mangle]
strchr(ptr: *const c_char, ch: c_int) -> *mut c_char27 pub unsafe extern "C" fn strchr(ptr: *const c_char, ch: c_int) -> *mut c_char {
28 assert!(!ptr.is_null());
29 // SAFETY: `str` is a valid null terminated string.
30 let bytes = unsafe { CStr::from_ptr(ptr) }.to_bytes_with_nul();
31 let target = (ch & 0xff) as u8;
32 for c in bytes.iter() {
33 if *c == target {
34 return c as *const _ as *mut _;
35 }
36 }
37 null_mut()
38 }
39
40 /// char *strrchr(const char *str, int c);
41 ///
42 /// # Safety
43 ///
44 /// * `str` must be a valid null-terminated C string.
45 #[no_mangle]
strrchr(ptr: *const c_char, ch: c_int) -> *mut c_char46 pub unsafe extern "C" fn strrchr(ptr: *const c_char, ch: c_int) -> *mut c_char {
47 assert!(!ptr.is_null());
48 // SAFETY: `str` is a null terminated string.
49 let bytes = unsafe { CStr::from_ptr(ptr) }.to_bytes_with_nul();
50 let target = (ch & 0xff) as u8;
51 for c in bytes.iter().rev() {
52 if *c == target {
53 return c as *const _ as *mut _;
54 }
55 }
56 null_mut()
57 }
58
59 #[cfg(test)]
60 mod test {
61 use super::*;
62 use std::ffi::CString;
63
to_cstr(s: &str) -> CString64 fn to_cstr(s: &str) -> CString {
65 CString::new(s).unwrap()
66 }
67
do_strchr(input: &str, c: char) -> Option<usize>68 fn do_strchr(input: &str, c: char) -> Option<usize> {
69 let input_cstr = to_cstr(input);
70 // SAFETY: `input_cstr` is a null terminated string.
71 let result = unsafe { strchr(input_cstr.as_ptr(), c as c_int) };
72
73 if result.is_null() {
74 None
75 } else {
76 let start_ptr = input_cstr.as_ptr();
77 // SAFETY: `result` is a pointer within the string that `start_ptr` points to.
78 Some(unsafe { result.offset_from(start_ptr) as usize })
79 }
80 }
81
do_strrchr(input: &str, c: char) -> Option<usize>82 fn do_strrchr(input: &str, c: char) -> Option<usize> {
83 let input_cstr = to_cstr(input);
84 // SAFETY: `input_cstr` is a null terminated string.
85 let result = unsafe { strrchr(input_cstr.as_ptr(), c as c_int) };
86
87 if result.is_null() {
88 None
89 } else {
90 let start_ptr = input_cstr.as_ptr();
91 // SAFETY: `result` is a pointer within the string that `start_ptr` points to.
92 Some(unsafe { result.offset_from(start_ptr) as usize })
93 }
94 }
95
96 // strchr tests
97
98 #[test]
strchr_find_first_occurrence()99 fn strchr_find_first_occurrence() {
100 let offset = do_strchr("hello", 'e');
101 assert_eq!(offset, Some(1));
102 }
103
104 #[test]
strchr_find_first_occurrence_special_character()105 fn strchr_find_first_occurrence_special_character() {
106 let offset = do_strchr("he!lo", '!');
107 assert_eq!(offset, Some(2));
108 }
109
110 #[test]
strchr_character_not_present()111 fn strchr_character_not_present() {
112 let offset = do_strchr("hello", 'z');
113 assert_eq!(offset, None);
114 }
115
116 #[test]
strchr_find_first_occurrence_at_start()117 fn strchr_find_first_occurrence_at_start() {
118 let offset = do_strchr("hello", 'h');
119 assert_eq!(offset, Some(0));
120 }
121
122 #[test]
strchr_find_first_occurrence_at_end()123 fn strchr_find_first_occurrence_at_end() {
124 let offset = do_strchr("hello", 'o');
125 assert_eq!(offset, Some(4));
126 }
127
128 #[test]
strchr_empty_string()129 fn strchr_empty_string() {
130 let offset = do_strchr("", 'a');
131 assert_eq!(offset, None);
132 }
133
134 #[test]
strchr_find_first_occurrence_multiple()135 fn strchr_find_first_occurrence_multiple() {
136 let offset = do_strchr("hellohello", 'l');
137 assert_eq!(offset, Some(2));
138 }
139
140 #[test]
strchr_case_sensitivity()141 fn strchr_case_sensitivity() {
142 let offset = do_strchr("Hello", 'h');
143 assert_eq!(offset, None);
144 }
145
146 #[test]
strchr_find_null_character()147 fn strchr_find_null_character() {
148 let offset = do_strchr("Hello", '\0');
149 assert_eq!(offset, Some(5));
150 }
151
152 // strrchr tests
153
154 #[test]
strrchr_find_last_occurrence()155 fn strrchr_find_last_occurrence() {
156 let offset = do_strrchr("hello", 'l');
157 assert_eq!(offset, Some(3));
158 }
159
160 #[test]
strrchr_find_last_occurrence_special_character()161 fn strrchr_find_last_occurrence_special_character() {
162 let offset = do_strrchr("he!lo!lo", '!');
163 assert_eq!(offset, Some(5));
164 }
165
166 #[test]
strrchr_character_not_present()167 fn strrchr_character_not_present() {
168 let offset = do_strrchr("hello", 'z');
169 assert_eq!(offset, None);
170 }
171
172 #[test]
strrchr_find_last_occurrence_at_start()173 fn strrchr_find_last_occurrence_at_start() {
174 let offset = do_strrchr("hello", 'h');
175 assert_eq!(offset, Some(0));
176 }
177
178 #[test]
strrchr_find_last_occurrence_at_end()179 fn strrchr_find_last_occurrence_at_end() {
180 let offset = do_strrchr("hello", 'o');
181 assert_eq!(offset, Some(4));
182 }
183
184 #[test]
strrchr_empty_string()185 fn strrchr_empty_string() {
186 let offset = do_strrchr("", 'a');
187 assert_eq!(offset, None);
188 }
189
190 #[test]
strrchr_find_last_occurrence_multiple()191 fn strrchr_find_last_occurrence_multiple() {
192 let offset = do_strrchr("hellohello", 'l');
193 assert_eq!(offset, Some(8));
194 }
195
196 #[test]
strrchr_case_sensitivity()197 fn strrchr_case_sensitivity() {
198 let offset = do_strrchr("Hello", 'h');
199 assert_eq!(offset, None);
200 }
201
202 #[test]
strrchr_find_null_character()203 fn strrchr_find_null_character() {
204 let offset = do_strchr("Hello", '\0');
205 assert_eq!(offset, Some(5));
206 }
207 }
208