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 strcmp libc functions family.
16 //! https://en.cppreference.com/w/c/string/byte/strncmp
17
18 use core::cmp::Ordering;
19 use core::ffi::{c_char, c_int, CStr};
20
21 /// int strcmp(const char *s1, const char *s2);
22 ///
23 /// # Safety
24 ///
25 /// * `s1` and `s2` must be valid pointers to null terminated C strings.
26 #[no_mangle]
strcmp(s1: *const c_char, s2: *const c_char) -> c_int27 pub unsafe extern "C" fn strcmp(s1: *const c_char, s2: *const c_char) -> c_int {
28 // SAFETY: `s1` and `s2` are valid null-terminated strings. References are only used
29 // within function.
30 let (lhs, rhs) = unsafe { (CStr::from_ptr(s1.cast()), CStr::from_ptr(s2.cast())) };
31 Ord::cmp(lhs, rhs) as _
32 }
33
34 /// int strncmp(const char *s1, const char *s2, size_t n);
35 ///
36 /// # Safety
37 ///
38 /// * `s1` and `s2` must be at least nth sized or null terminated arrays.
39 #[no_mangle]
strncmp(s1: *const c_char, s2: *const c_char, n: usize) -> c_int40 pub unsafe extern "C" fn strncmp(s1: *const c_char, s2: *const c_char, n: usize) -> c_int {
41 for i in 0..n {
42 // SAFETY: `i` is always within the bounds of `s1` and `s2` because it is limited by `n`,
43 // and this statement is unreachable if a null character is already encountered in `s1`
44 // or `s2`.
45 let (l, r) = unsafe { (*s1.add(i), *s2.add(i)) };
46
47 let cmp = l.cmp(&r);
48 if cmp != Ordering::Equal || l == 0 {
49 return cmp as _;
50 }
51 }
52 Ordering::Equal as _
53 }
54
55 #[cfg(test)]
56 mod test {
57 use super::*;
58 use std::ffi::CString;
59
to_cstr(s: &str) -> CString60 fn to_cstr(s: &str) -> CString {
61 CString::new(s).unwrap()
62 }
63
do_strcmp(left: &str, right: &str) -> c_int64 fn do_strcmp(left: &str, right: &str) -> c_int {
65 let left_cstr = to_cstr(left);
66 let right_cstr = to_cstr(right);
67 // SAFETY: `left_cstr` and `right_cstr` are null terminated strings.
68 unsafe { strcmp(left_cstr.as_ptr(), right_cstr.as_ptr()) }
69 }
70
do_strncmp(left: &str, right: &str, n: usize) -> c_int71 fn do_strncmp(left: &str, right: &str, n: usize) -> c_int {
72 let left_cstr = to_cstr(left);
73 let right_cstr = to_cstr(right);
74 // SAFETY: `left_cstr` and `right_cstr` are null terminated strings.
75 unsafe { strncmp(left_cstr.as_ptr(), right_cstr.as_ptr(), n) }
76 }
77
do_strncmp_bytes(left: &[u8], right: &[u8], n: usize) -> c_int78 fn do_strncmp_bytes(left: &[u8], right: &[u8], n: usize) -> c_int {
79 // SAFETY: `left` and `right` are not null.
80 unsafe { strncmp(left.as_ptr().cast(), right.as_ptr().cast(), n) }
81 }
82
83 // strcmp tests
84
85 #[test]
strcmp_same()86 fn strcmp_same() {
87 assert_eq!(do_strcmp("first", "first"), 0);
88 }
89
90 #[test]
strcmp_same_special_characters()91 fn strcmp_same_special_characters() {
92 assert_eq!(do_strcmp("!@#", "!@#"), 0);
93 }
94
95 #[test]
strcmp_left_smaller()96 fn strcmp_left_smaller() {
97 assert_eq!(do_strcmp("first", "second"), -1);
98 }
99
100 #[test]
strcmp_left_is_prefix_of_right()101 fn strcmp_left_is_prefix_of_right() {
102 assert_eq!(do_strcmp("first", "firstly"), -1);
103 }
104
105 #[test]
strcmp_right_is_prefix_of_left()106 fn strcmp_right_is_prefix_of_left() {
107 assert_eq!(do_strcmp("firstly", "first"), 1);
108 }
109
110 #[test]
strcmp_empty()111 fn strcmp_empty() {
112 assert_eq!(do_strcmp("", ""), 0);
113 }
114
115 #[test]
strcmp_empty_vs_non_empty()116 fn strcmp_empty_vs_non_empty() {
117 assert_eq!(do_strcmp("", "nonempty"), -1);
118 }
119
120 #[test]
strcmp_non_empty_vs_empty()121 fn strcmp_non_empty_vs_empty() {
122 assert_eq!(do_strcmp("nonempty", ""), 1);
123 }
124
125 #[test]
strcmp_case_sensitivity()126 fn strcmp_case_sensitivity() {
127 assert_eq!(do_strcmp("First", "first"), -1);
128 }
129
130 // strncmp tests
131
132 #[test]
strncmp_same_exact_length()133 fn strncmp_same_exact_length() {
134 assert_eq!(do_strncmp("hello", "hello", 5), 0);
135 }
136
137 #[test]
strncmp_same_partial_length()138 fn strncmp_same_partial_length() {
139 assert_eq!(do_strncmp("hello", "hello", 3), 0);
140 }
141
142 #[test]
strncmp_same_overflow()143 fn strncmp_same_overflow() {
144 assert_eq!(do_strncmp("hello", "hello", 100), 0);
145 }
146
147 #[test]
strncmp_same_special_characters()148 fn strncmp_same_special_characters() {
149 assert_eq!(do_strncmp("!@#", "!@#", 3), 0);
150 }
151
152 #[test]
strncmp_different_exact_length()153 fn strncmp_different_exact_length() {
154 assert_eq!(do_strncmp("hello", "world", 5), -1);
155 }
156
157 #[test]
strncmp_different_partial_length()158 fn strncmp_different_partial_length() {
159 assert_eq!(do_strncmp("hello", "world", 3), -1);
160 }
161
162 #[test]
strncmp_left_is_prefix_of_right()163 fn strncmp_left_is_prefix_of_right() {
164 assert_eq!(do_strncmp("abc", "abcdef", 6), -1);
165 }
166
167 #[test]
strncmp_right_is_prefix_of_left()168 fn strncmp_right_is_prefix_of_left() {
169 assert_eq!(do_strncmp("abcdef", "abc", 6), 1);
170 }
171
172 #[test]
strncmp_empty_strings()173 fn strncmp_empty_strings() {
174 assert_eq!(do_strncmp("", "", 5), 0);
175 }
176
177 #[test]
strncmp_empty_vs_non_empty()178 fn strncmp_empty_vs_non_empty() {
179 assert_eq!(do_strncmp("", "hello", 5), -1);
180 }
181
182 #[test]
strncmp_non_empty_vs_empty()183 fn strncmp_non_empty_vs_empty() {
184 assert_eq!(do_strncmp("hello", "", 5), 1);
185 }
186
187 #[test]
strncmp_case_sensitivity()188 fn strncmp_case_sensitivity() {
189 assert_eq!(do_strncmp("Hello", "hello", 5), -1);
190 }
191
192 #[test]
strncmp_bytes_array_same_exact_length()193 fn strncmp_bytes_array_same_exact_length() {
194 assert_eq!(do_strncmp_bytes(b"hello", b"hello", 5), 0);
195 }
196
197 #[test]
strncmp_bytes_array_right_terminated()198 fn strncmp_bytes_array_right_terminated() {
199 assert_eq!(do_strncmp_bytes(b"hello", b"hel\0", 5), 1);
200 }
201
202 #[test]
strncmp_bytes_array_left_terminated()203 fn strncmp_bytes_array_left_terminated() {
204 assert_eq!(do_strncmp_bytes(b"hel\0", b"hello", 5), -1);
205 }
206 }
207