// Copyright 2024, The Android Open Source Project // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. //! This library provides implementation for strchr libc functions family. //! https://en.cppreference.com/w/c/string/byte/strchr use core::ffi::{c_char, c_int, CStr}; use core::ptr::null_mut; /// char *strchr(const char *str, int c); /// /// # Safety /// /// * `str` must be a valid null-terminated C string. #[no_mangle] pub unsafe extern "C" fn strchr(ptr: *const c_char, ch: c_int) -> *mut c_char { assert!(!ptr.is_null()); // SAFETY: `str` is a valid null terminated string. let bytes = unsafe { CStr::from_ptr(ptr) }.to_bytes_with_nul(); let target = (ch & 0xff) as u8; for c in bytes.iter() { if *c == target { return c as *const _ as *mut _; } } null_mut() } /// char *strrchr(const char *str, int c); /// /// # Safety /// /// * `str` must be a valid null-terminated C string. #[no_mangle] pub unsafe extern "C" fn strrchr(ptr: *const c_char, ch: c_int) -> *mut c_char { assert!(!ptr.is_null()); // SAFETY: `str` is a null terminated string. let bytes = unsafe { CStr::from_ptr(ptr) }.to_bytes_with_nul(); let target = (ch & 0xff) as u8; for c in bytes.iter().rev() { if *c == target { return c as *const _ as *mut _; } } null_mut() } #[cfg(test)] mod test { use super::*; use std::ffi::CString; fn to_cstr(s: &str) -> CString { CString::new(s).unwrap() } fn do_strchr(input: &str, c: char) -> Option { let input_cstr = to_cstr(input); // SAFETY: `input_cstr` is a null terminated string. let result = unsafe { strchr(input_cstr.as_ptr(), c as c_int) }; if result.is_null() { None } else { let start_ptr = input_cstr.as_ptr(); // SAFETY: `result` is a pointer within the string that `start_ptr` points to. Some(unsafe { result.offset_from(start_ptr) as usize }) } } fn do_strrchr(input: &str, c: char) -> Option { let input_cstr = to_cstr(input); // SAFETY: `input_cstr` is a null terminated string. let result = unsafe { strrchr(input_cstr.as_ptr(), c as c_int) }; if result.is_null() { None } else { let start_ptr = input_cstr.as_ptr(); // SAFETY: `result` is a pointer within the string that `start_ptr` points to. Some(unsafe { result.offset_from(start_ptr) as usize }) } } // strchr tests #[test] fn strchr_find_first_occurrence() { let offset = do_strchr("hello", 'e'); assert_eq!(offset, Some(1)); } #[test] fn strchr_find_first_occurrence_special_character() { let offset = do_strchr("he!lo", '!'); assert_eq!(offset, Some(2)); } #[test] fn strchr_character_not_present() { let offset = do_strchr("hello", 'z'); assert_eq!(offset, None); } #[test] fn strchr_find_first_occurrence_at_start() { let offset = do_strchr("hello", 'h'); assert_eq!(offset, Some(0)); } #[test] fn strchr_find_first_occurrence_at_end() { let offset = do_strchr("hello", 'o'); assert_eq!(offset, Some(4)); } #[test] fn strchr_empty_string() { let offset = do_strchr("", 'a'); assert_eq!(offset, None); } #[test] fn strchr_find_first_occurrence_multiple() { let offset = do_strchr("hellohello", 'l'); assert_eq!(offset, Some(2)); } #[test] fn strchr_case_sensitivity() { let offset = do_strchr("Hello", 'h'); assert_eq!(offset, None); } #[test] fn strchr_find_null_character() { let offset = do_strchr("Hello", '\0'); assert_eq!(offset, Some(5)); } // strrchr tests #[test] fn strrchr_find_last_occurrence() { let offset = do_strrchr("hello", 'l'); assert_eq!(offset, Some(3)); } #[test] fn strrchr_find_last_occurrence_special_character() { let offset = do_strrchr("he!lo!lo", '!'); assert_eq!(offset, Some(5)); } #[test] fn strrchr_character_not_present() { let offset = do_strrchr("hello", 'z'); assert_eq!(offset, None); } #[test] fn strrchr_find_last_occurrence_at_start() { let offset = do_strrchr("hello", 'h'); assert_eq!(offset, Some(0)); } #[test] fn strrchr_find_last_occurrence_at_end() { let offset = do_strrchr("hello", 'o'); assert_eq!(offset, Some(4)); } #[test] fn strrchr_empty_string() { let offset = do_strrchr("", 'a'); assert_eq!(offset, None); } #[test] fn strrchr_find_last_occurrence_multiple() { let offset = do_strrchr("hellohello", 'l'); assert_eq!(offset, Some(8)); } #[test] fn strrchr_case_sensitivity() { let offset = do_strrchr("Hello", 'h'); assert_eq!(offset, None); } #[test] fn strrchr_find_null_character() { let offset = do_strchr("Hello", '\0'); assert_eq!(offset, Some(5)); } }