diff --git a/Cargo.toml b/Cargo.toml index cd84537..bf0878a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,7 +2,7 @@ name = "tinyrlibc" version = "0.3.0" authors = ["Jonathan 'theJPster' Pallant "] -edition = "2018" +edition = "2021" description = "Tiny, incomplete C library for bare-metal targets, written in Stable (but Unsafe) Rust" license-file = "LICENCES.md" readme = "README.md" diff --git a/LICENCES.md b/LICENCES.md index 7a84c0d..afa49d8 100644 --- a/LICENCES.md +++ b/LICENCES.md @@ -52,4 +52,33 @@ No contributor can revoke this license. ***As far as the law allows, this software comes as is, without any warranty or condition, and no contributor will be liable to anyone for any damages related to this -software or this license, under any kind of legal claim.*** \ No newline at end of file +software or this license, under any kind of legal claim.*** + +# Copyright (c) 1990 Regents of the University of California + +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. +3. [rescinded 22 July 1999] +4. Neither the name of the University nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +SUCH DAMAGE. diff --git a/build.rs b/build.rs index ab872d2..2e32da3 100644 --- a/build.rs +++ b/build.rs @@ -1,5 +1,3 @@ -use cc; - fn main() { // Build our snprintf substitute (which has to be C as Rust doesn't do varargs) cc::Build::new() diff --git a/src/atoi.rs b/src/atoi.rs index 0ab4d55..56642f5 100644 --- a/src/atoi.rs +++ b/src/atoi.rs @@ -10,9 +10,9 @@ use crate::{strtol, CChar, CInt, CLong}; /// /// ``` /// use tinyrlibc::atoi; -/// assert_eq!(unsafe { atoi(b"123".as_ptr()) }, 123); -/// assert_eq!(unsafe { atoi(b"123x".as_ptr()) }, 123); -/// assert_eq!(unsafe { atoi(b"".as_ptr()) }, 0); +/// assert_eq!(unsafe { atoi(b"123\0".as_ptr()) }, 123); +/// assert_eq!(unsafe { atoi(b"123x\0".as_ptr()) }, 123); +/// assert_eq!(unsafe { atoi(b"\0".as_ptr()) }, 0); /// ``` #[no_mangle] pub unsafe extern "C" fn atoi(s: *const CChar) -> CInt { diff --git a/src/snprintf.c b/src/snprintf.c index d954e04..8c88091 100644 --- a/src/snprintf.c +++ b/src/snprintf.c @@ -62,6 +62,11 @@ extern int32_t itoa(int64_t i, char* s, size_t s_len, uint8_t radix); */ extern int32_t utoa(uint64_t i, char* s, size_t s_len, uint8_t radix); +/** + * This is provided by `strtoul.rs`. It converts a string to a long. + */ +extern unsigned long int strtoul(const char* str, const char* restrict* endptr, int base); + /* ======================================================================== * * * Public Function Definitions @@ -91,7 +96,7 @@ extern int32_t utoa(uint64_t i, char* s, size_t s_len, uint8_t radix); * - f (decimal floating point) * - g (the shorter of %e and %f) * - G (the shorter of %E and %f) - * - qualifiers: L, width, precision, -, +, space-pad, zero-pad, etc + * - qualifiers: L, width, (non-string) precision, -, +, space-pad, zero-pad, etc * * @param str the output buffer to write to * @param size the size of the output buffer @@ -107,6 +112,8 @@ int vsnprintf( bool is_escape = false; int is_long = 0; bool is_size_t = false; + unsigned long precision = -1; + while ( *fmt ) { if ( is_escape ) @@ -315,12 +322,40 @@ int vsnprintf( // Render %s { const char *s = va_arg( ap, const char* ); - for ( const char* p = s; *p != '\0'; p++ ) + unsigned long count = precision; + + while (count > 0 && *s != '\0') { - write_output( *p, str, size, &written ); + write_output(*s, str, size, &written); + + s++; + if (precision != (unsigned long)-1) { + count--; + } } } break; + case '.': + // Render a precision specifier + { + // Next up is either a number or a '*' that signifies that the number is in the arguments list + char next = *++fmt; + + if (next == '*') + { + precision = va_arg( ap, int ); + } + else + { + precision = strtoul(fmt, &fmt, 10); + // Strtoul sets the fmt pointer to the char after the number, + // however the code expects the char before that. + fmt--; + } + + is_escape = true; + } + break; case '%': write_output( '%', str, size, &written ); break; @@ -329,6 +364,11 @@ int vsnprintf( break; } fmt++; + + if (!is_escape) { + // Reset precision if it hasn't just been assigned + precision = -1; + } } else { diff --git a/src/snprintf.rs b/src/snprintf.rs index 5bfc5f6..ac4be00 100644 --- a/src/snprintf.rs +++ b/src/snprintf.rs @@ -16,7 +16,9 @@ mod test { let mut buf = [b'\0'; 32]; assert_eq!( unsafe { snprintf(buf.as_mut_ptr(), buf.len(), "Hi\0".as_ptr()) }, - 2 + 2, + "{}", + String::from_utf8_lossy(&buf).escape_debug(), ); assert_eq!( unsafe { strcmp(buf.as_ptr() as *const u8, b"Hi\0" as *const u8) }, @@ -37,7 +39,9 @@ mod test { "World\0".as_ptr(), ) }, - 13 + 13, + "{}", + String::from_utf8_lossy(&buf).escape_debug(), ); assert_eq!( unsafe { strcmp(buf.as_ptr() as *const u8, b"Hello, World!\0" as *const u8) }, @@ -85,7 +89,9 @@ mod test { CULongLong::from(0xcafe1234u32), ) }, - 53 + 53, + "{}", + String::from_utf8_lossy(&buf).escape_debug(), ); assert_eq!( unsafe { @@ -97,4 +103,44 @@ mod test { 0 ); } + + #[test] + fn non_null_terminated_with_length() { + let mut buf = [b'\0'; 64]; + assert_eq!( + unsafe { + snprintf( + buf.as_mut_ptr(), + buf.len(), + "%.*s\0".as_ptr(), + 5, + "01234567890123456789\0".as_ptr(), + ) + }, + 5, + "{}", + String::from_utf8_lossy(&buf).escape_debug(), + ); + assert_eq!( + unsafe { strcmp(buf.as_ptr() as *const u8, b"01234\0" as *const u8,) }, + 0 + ); + assert_eq!( + unsafe { + snprintf( + buf.as_mut_ptr(), + buf.len(), + "%.10s\0".as_ptr(), + "01234567890123456789\0".as_ptr(), + ) + }, + 10, + "{}", + String::from_utf8_lossy(&buf).escape_debug(), + ); + assert_eq!( + unsafe { strcmp(buf.as_ptr() as *const u8, b"0123456789\0" as *const u8,) }, + 0 + ); + } } diff --git a/src/strstr.rs b/src/strstr.rs index 64eb58e..cb4ac9e 100644 --- a/src/strstr.rs +++ b/src/strstr.rs @@ -17,7 +17,7 @@ pub unsafe extern "C" fn strstr(haystack: *const CChar, needle: *const CChar) -> } let mut len = 0; for (inner_idx, nec) in CStringIter::new(needle).enumerate() { - let hsc = *haystack_trim.offset(inner_idx as isize); + let hsc = *haystack_trim.add(inner_idx); if hsc != nec { break; } diff --git a/src/strtol.rs b/src/strtol.rs index f3dbf01..602c7e3 100644 --- a/src/strtol.rs +++ b/src/strtol.rs @@ -15,7 +15,7 @@ use crate::{CChar, CLong, CStringIter}; pub unsafe extern "C" fn strtol(s: *const CChar) -> CLong { let mut result: CLong = 0; for c in CStringIter::new(s) { - if c >= b'0' && c <= b'9' { + if (b'0'..=b'9').contains(&c) { result *= 10; result += (c - b'0') as CLong; } else { diff --git a/src/strtoul.rs b/src/strtoul.rs index e1587d1..ba6fc36 100644 --- a/src/strtoul.rs +++ b/src/strtoul.rs @@ -1,67 +1,191 @@ -//! Rust implementation of C library function `strtoul` +//! Copyright (c) 1990 Regents of the University of California. +//! All rights reserved. //! -//! Copyright (c) Jonathan 'theJPster' Pallant 2019 -//! Licensed under the Blue Oak Model Licence 1.0.0 +//! Redistribution and use in source and binary forms, with or without +//! modification, are permitted provided that the following conditions +//! are met: +//! 1. Redistributions of source code must retain the above copyright +//! notice, this list of conditions and the following disclaimer. +//! 2. Redistributions in binary form must reproduce the above copyright +//! notice, this list of conditions and the following disclaimer in the +//! documentation and/or other materials provided with the distribution. +//! 3. [rescinded 22 July 1999] +//! 4. Neither the name of the University nor the names of its contributors +//! may be used to endorse or promote products derived from this software +//! without specific prior written permission. +//! +//! THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND +//! ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +//! IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +//! ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE +//! FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +//! DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +//! OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +//! HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +//! LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +//! OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +//! SUCH DAMAGE. +//! +//! Translated from https://github.com/gcc-mirror/gcc/blob/97d1ed67fc6a5773c8c00875bfa3616a457cf5f9/libiberty/strtoul.c -use crate::{CChar, CStringIter, CULong}; +use crate::{CChar, CInt, CLong, CULong}; -/// Rust implementation of C library function `strtoul`. +/// Rust implementation of C library function [`strtoul`](https://cplusplus.com/reference/cstdlib/strtoul/). +/// +/// Passing NULL (core::ptr::null()) gives undefined behaviour. /// -/// Takes a null-terminated string and interprets it as a decimal integer. -/// This integer is returned as a `CULong`. Parsing stops when the first -/// non-digit ASCII byte is seen. If no valid ASCII digit bytes are seen, this -/// function returns zero. +/// Convert a string to an unsigned long integer. +/// +/// Ignores `locale' stuff. Assumes that the upper and lower case +/// alphabets and digits are each contiguous. #[no_mangle] -pub unsafe extern "C" fn strtoul(s: *const CChar) -> CULong { - let mut result: CULong = 0; - for c in CStringIter::new(s) { - if c >= b'0' && c <= b'9' { - result *= 10; - result += (c - b'0') as CULong; +pub unsafe extern "C" fn strtoul( + nptr: *const CChar, + endptr: *mut *const CChar, + mut base: CInt, +) -> CULong { + let mut s = nptr; + + let mut c = *s; + s = s.offset(1); + while isspace(c) { + c = *s; + s = s.offset(1); + } + + let neg = if c == b'-' { + c = *s; + s = s.offset(1); + true + } else { + if c == b'+' { + c = *s; + s = s.offset(1); + } + false + }; + + if (base == 0 || base == 16) && c == b'0' && (*s == b'x' || *s == b'X') { + c = *s.offset(1); + s = s.offset(2); + base = 16; + } + + if base == 0 { + base = if c == b'0' { 8 } else { 10 }; + } + + let cutoff = CULong::MAX / base as CULong; + let cutlim = CULong::MAX % base as CULong; + + let mut acc = 0; + let mut any = 0; + + loop { + if isdigit(c) { + c -= b'0'; + } else if isalpha(c) { + c -= if isupper(c) { b'A' - 10 } else { b'a' - 10 }; } else { break; } + + if c as CInt >= base { + break; + } + + if any < 0 { + c = *s; + s = s.offset(1); + continue; + } + + if acc > cutoff || (acc == cutoff && c as CULong > cutlim) { + any = -1; + acc = CULong::MAX; + } else { + any = 1; + acc *= base as CULong; + acc += c as CULong; + } + + c = *s; + s = s.offset(1); } - result + if neg && any > 0 { + acc = -(acc as CLong) as _; + } + + if !endptr.is_null() { + (*endptr) = if any != 0 { + s.offset(-1) + } else { + core::ptr::null() + }; + } + + acc +} + +fn isspace(argument: CChar) -> bool { + // Rust doesn't support "\v" + const VERTICAL_TAB: u8 = 0x0B; + // Rust doesn't support "\f" + const FEED: u8 = 0x0C; + const SPACE_CHARACTERS: [u8; 6] = [b' ', b'\n', b'\t', VERTICAL_TAB, FEED, b'\r']; + + SPACE_CHARACTERS.contains(&argument) +} + +fn isdigit(argument: CChar) -> bool { + (b'0'..=b'9').contains(&argument) +} + +fn isalpha(argument: CChar) -> bool { + (b'a'..=b'z').contains(&argument) || (b'A'..=b'Z').contains(&argument) +} + +fn isupper(argument: CChar) -> bool { + (b'A'..=b'Z').contains(&argument) } #[cfg(test)] -mod test { - use super::strtoul; +mod tests { + use core::ptr::null_mut; - #[test] - fn empty() { - let result = unsafe { strtoul(b"\0".as_ptr()) }; - assert_eq!(result, 0); - } + use super::*; #[test] - fn non_digit() { - let result = unsafe { strtoul(b"1234x\0".as_ptr()) }; - assert_eq!(result, 1234); - } + fn parse_multi_string() { + let string = b"10 200000000000000000000000000000 30 -40\0"; - #[test] - fn bad_number() { - let result = unsafe { strtoul(b"x\0".as_ptr()) }; - assert_eq!(result, 0); - } + let mut s = string.as_ptr(); - #[test] - fn one() { - let result = unsafe { strtoul(b"1\0".as_ptr()) }; - assert_eq!(result, 1); - } + let results = [ + (10, unsafe { s.offset(2) }), + (CULong::MAX, unsafe { s.offset(33) }), + (30, unsafe { s.offset(36) }), + (-40i32 as CULong, unsafe { s.offset(40) }), + ]; - #[test] - fn hundredish() { - let result = unsafe { strtoul(b"123\0".as_ptr()) }; - assert_eq!(result, 123); + for (result_number, result_ptr) in results { + let number = unsafe { strtoul(s, &mut s as *mut _, 10) }; + + assert_eq!(number, result_number); + assert_eq!(s, result_ptr); + } } #[test] - fn big_long() { - let result = unsafe { strtoul(b"2147483647\0".as_ptr()) }; - assert_eq!(result, 2147483647); + fn parse_hex() { + assert_eq!( + unsafe { strtoul(b"0xAA123\0".as_ptr(), null_mut(), 0) }, + 0xAA123 + ); + assert_eq!(unsafe { strtoul(b"0X00\0".as_ptr(), null_mut(), 0) }, 0x00); + assert_eq!( + unsafe { strtoul(b"-0x123456F\0".as_ptr(), null_mut(), 0) }, + (-0x123456Fi32) as _ + ); } }