Skip to content

Commit

Permalink
fix: add more lints, fix miri
Browse files Browse the repository at this point in the history
  • Loading branch information
DaniPopes committed Oct 2, 2023
1 parent 89b4ccb commit d510bfd
Show file tree
Hide file tree
Showing 10 changed files with 91 additions and 37 deletions.
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ keywords = ["hex", "bytes", "fmt"]
documentation = "https://docs.rs/const-hex"
homepage = "https://github.com/danipopes/const-hex"
repository = "https://github.com/danipopes/const-hex"
exclude = [".github/", "benches/", "fuzz/", "tests/"]

[package.metadata.docs.rs]
all-features = true
Expand Down
File renamed without changes.
2 changes: 2 additions & 0 deletions src/aarch64.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
#![allow(unsafe_op_in_unsafe_fn)]

use crate::generic;
use core::arch::aarch64::*;

Expand Down
3 changes: 2 additions & 1 deletion src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ use core::fmt;
pub enum FromHexError {
/// An invalid character was found. Valid ones are: `0...9`, `a...f`
/// or `A...F`.
#[allow(missing_docs)]
InvalidHexCharacter { c: char, index: usize },

/// A hex string's length needs to be even, as two digits correspond to
Expand All @@ -24,7 +25,7 @@ pub enum FromHexError {
impl std::error::Error for FromHexError {}

impl fmt::Display for FromHexError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match *self {
FromHexError::InvalidHexCharacter { c, index } => {
write!(f, "Invalid character {c:?} at position {index}")
Expand Down
70 changes: 49 additions & 21 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,19 @@
#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))]
#![cfg_attr(feature = "nightly", feature(core_intrinsics, inline_const))]
#![cfg_attr(feature = "portable-simd", feature(portable_simd))]
#![warn(
missing_copy_implementations,
missing_debug_implementations,
missing_docs,
unreachable_pub,
unsafe_op_in_unsafe_fn,
clippy::missing_const_for_fn,
clippy::missing_inline_in_public_items,
clippy::all,
rustdoc::all
)]
#![cfg_attr(not(test), warn(unused_crate_dependencies))]
#![deny(unused_must_use, rust_2018_idioms)]
#![allow(
clippy::cast_lossless,
clippy::inline_always,
Expand All @@ -32,12 +45,17 @@
extern crate alloc;

use cfg_if::cfg_if;
use core::fmt;
use core::slice;
use core::str;

#[cfg(feature = "alloc")]
use alloc::{string::String, vec::Vec};

// `cpufeatures` may be unused when `force-generic` is enabled.
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
use cpufeatures as _;

// The main encoding and decoding functions.
cfg_if! {
if #[cfg(feature = "force-generic")] {
Expand All @@ -60,7 +78,7 @@ cfg_if! {
// Otherwise, use our own with the more optimized implementation.
cfg_if! {
if #[cfg(feature = "hex")] {
pub extern crate hex;
pub use hex;

#[doc(inline)]
pub use hex::{FromHex, FromHexError, ToHex};
Expand Down Expand Up @@ -161,12 +179,13 @@ pub const HEX_DECODE_LUT: &[u8; 256] = &make_decode_lut();
/// ```
#[must_use]
#[repr(C)]
#[derive(Clone)]
pub struct Buffer<const N: usize, const PREFIX: bool = false> {
// Workaround for Rust issue #76560:
// https://github.com/rust-lang/rust/issues/76560
// This would ideally be `[u8; (N + PREFIX as usize) * 2]`
prefix: [u8; 2],
bytes: [u16; N],
bytes: [[u8; 2]; N],
}

impl<const N: usize, const PREFIX: bool> Default for Buffer<N, PREFIX> {
Expand All @@ -176,24 +195,27 @@ impl<const N: usize, const PREFIX: bool> Default for Buffer<N, PREFIX> {
}
}

impl<const N: usize, const PREFIX: bool> Clone for Buffer<N, PREFIX> {
impl<const N: usize, const PREFIX: bool> fmt::Debug for Buffer<N, PREFIX> {
#[inline]
fn clone(&self) -> Self {
Self::new()
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_tuple("Buffer").field(&self.as_str()).finish()
}
}

impl<const N: usize, const PREFIX: bool> Buffer<N, PREFIX> {
/// The length of the buffer in bytes.
pub const LEN: usize = (N + PREFIX as usize) * 2;

const _ASSERT_SIZE: () = assert!(core::mem::size_of::<Self>() == 2 + N, "invalid size");
const _ASSERT_ALIGNMENT: () = assert!(core::mem::align_of::<Self>() == 1, "invalid alignment");

/// This is a cheap operation; you don't need to worry about reusing buffers
/// for efficiency.
#[inline]
pub const fn new() -> Self {
Self {
prefix: if PREFIX { [b'0', b'x'] } else { [0, 0] },
bytes: [0; N],
bytes: [[0; 2]; N],
}
}

Expand All @@ -214,7 +236,8 @@ impl<const N: usize, const PREFIX: bool> Buffer<N, PREFIX> {
let mut i = 0;
while i < N {
let (high, low) = byte2hex::<UPPER>(array[i]);
self.bytes[i] = u16::from_le_bytes([high, low]);
self.bytes[i][0] = high;
self.bytes[i][1] = low;
i += 1;
}
self
Expand Down Expand Up @@ -243,6 +266,7 @@ impl<const N: usize, const PREFIX: bool> Buffer<N, PREFIX> {
///
/// If the slice is not exactly `N` bytes long.
#[track_caller]
#[inline]
pub fn format_slice<T: AsRef<[u8]>>(&mut self, slice: T) -> &mut str {
self.format_slice_inner::<false>(slice.as_ref())
}
Expand All @@ -254,6 +278,7 @@ impl<const N: usize, const PREFIX: bool> Buffer<N, PREFIX> {
///
/// If the slice is not exactly `N` bytes long.
#[track_caller]
#[inline]
pub fn format_slice_upper<T: AsRef<[u8]>>(&mut self, slice: T) -> &mut str {
self.format_slice_inner::<true>(slice.as_ref())
}
Expand Down Expand Up @@ -320,7 +345,7 @@ impl<const N: usize, const PREFIX: bool> Buffer<N, PREFIX> {
///
/// See Rust tracking issue [#76001](https://github.com/rust-lang/rust/issues/76001).
#[inline]
pub fn as_byte_array<const LEN: usize>(&self) -> &[u8; LEN] {
pub const fn as_byte_array<const LEN: usize>(&self) -> &[u8; LEN] {
maybe_const_assert!(LEN == Self::LEN, "`LEN` must be equal to `Self::LEN`");
// SAFETY: [u16; N] is layout-compatible with [u8; N * 2].
unsafe { &*self.as_ptr().cast::<[u8; LEN]>() }
Expand Down Expand Up @@ -366,6 +391,7 @@ impl<const N: usize, const PREFIX: bool> Buffer<N, PREFIX> {
/// # Safety
///
/// See [`as_mut_bytes`](Buffer::as_mut_bytes).
#[inline]
pub unsafe fn buffer(&mut self) -> &mut [u8] {
unsafe { slice::from_raw_parts_mut(self.bytes.as_mut_ptr().cast(), N * 2) }
}
Expand All @@ -376,11 +402,7 @@ impl<const N: usize, const PREFIX: bool> Buffer<N, PREFIX> {
/// function returns, or else it will end up pointing to garbage.
#[inline]
pub const fn as_ptr(&self) -> *const u8 {
if PREFIX {
self.prefix.as_ptr()
} else {
self.bytes.as_ptr().cast::<u8>()
}
unsafe { (self as *const Self).cast::<u8>().add(!PREFIX as usize * 2) }
}

/// Returns an unsafe mutable pointer to the slice's buffer.
Expand All @@ -389,11 +411,7 @@ impl<const N: usize, const PREFIX: bool> Buffer<N, PREFIX> {
/// function returns, or else it will end up pointing to garbage.
#[inline]
pub fn as_mut_ptr(&mut self) -> *mut u8 {
if PREFIX {
self.prefix.as_mut_ptr()
} else {
self.bytes.as_mut_ptr().cast::<u8>()
}
unsafe { (self as *mut Self).cast::<u8>().add(!PREFIX as usize * 2) }
}
}

Expand Down Expand Up @@ -432,6 +450,7 @@ pub const fn const_encode<const N: usize, const PREFIX: bool>(
/// # Ok(())
/// # }
/// ```
#[inline]
pub fn encode_to_slice<T: AsRef<[u8]>>(input: T, output: &mut [u8]) -> Result<(), FromHexError> {
encode_to_slice_inner::<false>(input.as_ref(), output)
}
Expand All @@ -453,6 +472,7 @@ pub fn encode_to_slice<T: AsRef<[u8]>>(input: T, output: &mut [u8]) -> Result<()
/// # Ok(())
/// # }
/// ```
#[inline]
pub fn encode_to_slice_upper<T: AsRef<[u8]>>(
input: T,
output: &mut [u8],
Expand All @@ -474,6 +494,7 @@ pub fn encode_to_slice_upper<T: AsRef<[u8]>>(
/// assert_eq!(const_hex::encode([1, 2, 3, 15, 16]), "0102030f10");
/// ```
#[cfg(feature = "alloc")]
#[inline]
pub fn encode<T: AsRef<[u8]>>(data: T) -> String {
encode_inner::<false, false>(data.as_ref())
}
Expand All @@ -489,6 +510,7 @@ pub fn encode<T: AsRef<[u8]>>(data: T) -> String {
/// assert_eq!(const_hex::encode_upper([1, 2, 3, 15, 16]), "0102030F10");
/// ```
#[cfg(feature = "alloc")]
#[inline]
pub fn encode_upper<T: AsRef<[u8]>>(data: T) -> String {
encode_inner::<true, false>(data.as_ref())
}
Expand All @@ -504,6 +526,7 @@ pub fn encode_upper<T: AsRef<[u8]>>(data: T) -> String {
/// assert_eq!(const_hex::encode_prefixed([1, 2, 3, 15, 16]), "0x0102030f10");
/// ```
#[cfg(feature = "alloc")]
#[inline]
pub fn encode_prefixed<T: AsRef<[u8]>>(data: T) -> String {
encode_inner::<false, true>(data.as_ref())
}
Expand All @@ -519,6 +542,7 @@ pub fn encode_prefixed<T: AsRef<[u8]>>(data: T) -> String {
/// assert_eq!(const_hex::encode_upper_prefixed([1, 2, 3, 15, 16]), "0x0102030F10");
/// ```
#[cfg(feature = "alloc")]
#[inline]
pub fn encode_upper_prefixed<T: AsRef<[u8]>>(data: T) -> String {
encode_inner::<true, true>(data.as_ref())
}
Expand Down Expand Up @@ -551,6 +575,7 @@ pub fn encode_upper_prefixed<T: AsRef<[u8]>>(data: T) -> String {
/// assert!(const_hex::decode("foo").is_err());
/// ```
#[cfg(feature = "alloc")]
#[inline]
pub fn decode<T: AsRef<[u8]>>(input: T) -> Result<Vec<u8>, FromHexError> {
fn decode_inner(input: &[u8]) -> Result<Vec<u8>, FromHexError> {
if unlikely(input.len() % 2 != 0) {
Expand Down Expand Up @@ -589,6 +614,7 @@ pub fn decode<T: AsRef<[u8]>>(input: T) -> Result<Vec<u8>, FromHexError> {
/// const_hex::decode_to_slice("0x6b697769", &mut bytes).unwrap();
/// assert_eq!(&bytes, b"kiwi");
/// ```
#[inline]
pub fn decode_to_slice<T: AsRef<[u8]>>(input: T, output: &mut [u8]) -> Result<(), FromHexError> {
fn decode_to_slice_inner(input: &[u8], output: &mut [u8]) -> Result<(), FromHexError> {
if unlikely(input.len() % 2 != 0) {
Expand Down Expand Up @@ -644,8 +670,10 @@ mod generic {
pub(super) unsafe fn encode<const UPPER: bool>(input: &[u8], output: *mut u8) {
for (i, byte) in input.iter().enumerate() {
let (high, low) = byte2hex::<UPPER>(*byte);
output.add(i * 2).write(high);
output.add(i * 2 + 1).write(low);
unsafe {
output.add(i * 2).write(high);
output.add(i * 2 + 1).write(low);
}
}
}

Expand All @@ -657,7 +685,7 @@ mod generic {
pub(super) unsafe fn decode(input: &[u8], output: &mut [u8]) -> Result<(), FromHexError> {
macro_rules! next {
($var:ident, $i:expr) => {
let hex = *input.get_unchecked($i);
let hex = unsafe { *input.get_unchecked($i) };
let $var = HEX_DECODE_LUT[hex as usize];
if unlikely($var == u8::MAX) {
return Err(FromHexError::InvalidHexCharacter {
Expand Down
17 changes: 11 additions & 6 deletions src/portable_simd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ pub(super) unsafe fn encode<const UPPER: bool>(input: &[u8], output: *mut u8) {
let mut i = 0;
let (prefix, chunks, suffix) = input.as_simd::<CHUNK_SIZE>();

generic::encode::<UPPER>(prefix, output);
// SAFETY: ensured by caller.
unsafe { generic::encode::<UPPER>(prefix, output) };
i += prefix.len() * 2;

let hex_table = u8x16::from_array(*crate::get_chars_table::<UPPER>());
Expand All @@ -30,13 +31,17 @@ pub(super) unsafe fn encode<const UPPER: bool>(input: &[u8], output: *mut u8) {
let (hex_lo, hex_hi) = u8x16::interleave(hi, lo);

// Store result into the output buffer.
hex_lo.copy_to_slice(slice::from_raw_parts_mut(output.add(i), CHUNK_SIZE));
i += CHUNK_SIZE;
hex_hi.copy_to_slice(slice::from_raw_parts_mut(output.add(i), CHUNK_SIZE));
i += CHUNK_SIZE;
// SAFETY: ensured by caller.
unsafe {
hex_lo.copy_to_slice(slice::from_raw_parts_mut(output.add(i), CHUNK_SIZE));
i += CHUNK_SIZE;
hex_hi.copy_to_slice(slice::from_raw_parts_mut(output.add(i), CHUNK_SIZE));
i += CHUNK_SIZE;
}
}

generic::encode::<UPPER>(suffix, output.add(i));
// SAFETY: ensured by caller.
unsafe { generic::encode::<UPPER>(suffix, output.add(i)) };
}

pub(super) use generic::decode;
5 changes: 4 additions & 1 deletion src/serde.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ mod serialize {
/// is always even, each byte in data is always encoded using two hex digits.
/// Thus, the resulting string contains exactly twice as many bytes as the input
/// data.
#[inline]
pub fn serialize<S, T>(data: T, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
Expand All @@ -41,6 +42,7 @@ mod serialize {
/// Serializes `data` as hex string using uppercase characters.
///
/// Apart from the characters' casing, this works exactly like [`serialize`].
#[inline]
pub fn serialize_upper<S, T>(data: T, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
Expand All @@ -57,6 +59,7 @@ pub use serialize::{serialize, serialize_upper};
///
/// Both, upper and lower case characters are valid in the input string and can
/// even be mixed (e.g. `f9b4ca`, `F9B4CA` and `f9B4Ca` are all valid strings).
#[inline]
pub fn deserialize<'de, D, T>(deserializer: D) -> Result<T, D::Error>
where
D: Deserializer<'de>,
Expand All @@ -72,7 +75,7 @@ where
{
type Value = T;

fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("a hex encoded string")
}

Expand Down
1 change: 1 addition & 0 deletions src/traits.rs
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ impl<T: AsRef<[u8]>> ToHex for T {
/// # Ok::<(), const_hex::FromHexError>(())
/// ```
pub trait FromHex: Sized {
/// The associated error which can be returned from parsing.
type Error;

/// Creates an instance of type `Self` from the given hex string, or fails
Expand Down
2 changes: 2 additions & 0 deletions src/x86.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
#![allow(unsafe_op_in_unsafe_fn)]

use crate::generic;

#[cfg(target_arch = "x86")]
Expand Down
Loading

0 comments on commit d510bfd

Please sign in to comment.