blob: 857eab10f009949d95fa0817dfc65a49a9fc8c22 [file] [log] [blame]
// 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<usize> {
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<usize> {
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));
}
}