From c5ad3ce1070dd19e1b4111e5ef62134fde3586b1 Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Tue, 9 Aug 2022 21:48:42 +0200 Subject: [PATCH] Add static encoding strings --- objc2-encode/CHANGELOG.md | 3 + objc2-encode/src/encode.rs | 80 +++++++++++++++++++ objc2-encode/src/lib.rs | 3 - .../crates/test_encoding_cstr/Cargo.toml | 26 ++++++ .../expected/apple-aarch64.s | 13 +++ .../test_encoding_cstr/expected/apple-armv7.s | 14 ++++ .../expected/apple-armv7s.s | 14 ++++ .../test_encoding_cstr/expected/apple-x86.s | 14 ++++ .../expected/apple-x86_64.s | 14 ++++ .../test_encoding_cstr/expected/gnustep-x86.s | 18 +++++ .../expected/gnustep-x86_64.s | 18 +++++ .../crates/test_encoding_cstr/lib.rs | 7 ++ test-ui/src/main.rs | 6 ++ test-ui/ui-ignore/encode_cstr_not_ident.rs | 15 ++++ test-ui/ui-ignore/encode_cstr_too_long.rs | 17 ++++ tests/src/test_encode_utils.rs | 24 +++++- 16 files changed, 279 insertions(+), 7 deletions(-) create mode 100644 test-assembly/crates/test_encoding_cstr/Cargo.toml create mode 100644 test-assembly/crates/test_encoding_cstr/expected/apple-aarch64.s create mode 100644 test-assembly/crates/test_encoding_cstr/expected/apple-armv7.s create mode 100644 test-assembly/crates/test_encoding_cstr/expected/apple-armv7s.s create mode 100644 test-assembly/crates/test_encoding_cstr/expected/apple-x86.s create mode 100644 test-assembly/crates/test_encoding_cstr/expected/apple-x86_64.s create mode 100644 test-assembly/crates/test_encoding_cstr/expected/gnustep-x86.s create mode 100644 test-assembly/crates/test_encoding_cstr/expected/gnustep-x86_64.s create mode 100644 test-assembly/crates/test_encoding_cstr/lib.rs create mode 100644 test-ui/ui-ignore/encode_cstr_not_ident.rs create mode 100644 test-ui/ui-ignore/encode_cstr_too_long.rs diff --git a/objc2-encode/CHANGELOG.md b/objc2-encode/CHANGELOG.md index 8c003b249..8270a954a 100644 --- a/objc2-encode/CHANGELOG.md +++ b/objc2-encode/CHANGELOG.md @@ -6,6 +6,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ## Unreleased - YYYY-MM-DD +### Added +* Added `Encode::ENCODING_CSTR` for statically generating an encoding string. + ### Fixed * Fixed the encoding output and comparison of structs behind pointers. diff --git a/objc2-encode/src/encode.rs b/objc2-encode/src/encode.rs index d09a25a0c..a6ddf524f 100644 --- a/objc2-encode/src/encode.rs +++ b/objc2-encode/src/encode.rs @@ -6,7 +6,11 @@ use core::num::{ }; use core::ptr::NonNull; use core::sync::atomic; +#[cfg(feature = "std")] // TODO: Use `core` +use std::ffi::CStr; +use crate::helper::NestingLevel; +use crate::static_str::{static_encoding_str_array, static_encoding_str_len}; use crate::Encoding; /// Types that have an Objective-C type-encoding. @@ -16,6 +20,7 @@ use crate::Encoding; /// If your type is an opaque type you should not need to implement this; /// there you will only need [`RefEncode`]. /// +/// /// # Safety /// /// The type must be FFI-safe, meaning a C-compatible `repr` (`repr(C)`, @@ -33,6 +38,9 @@ use crate::Encoding; /// passed to Objective-C via. `objc2::msg_send!` their destructor will not be /// called! /// +/// Finally, you must not override [`ENCODING_CSTR`][Self::ENCODING_CSTR]. +/// +/// /// # Examples /// /// Implementing for a struct: @@ -69,6 +77,43 @@ use crate::Encoding; pub unsafe trait Encode { /// The Objective-C type-encoding for this type. const ENCODING: Encoding<'static>; + + #[doc(hidden)] + const __ENCODING_CSTR_LEN: usize = static_encoding_str_len(Self::ENCODING, NestingLevel::new()); + + #[doc(hidden)] + const __ENCODING_CSTR_ARRAY: [u8; 128] = { + if Self::__ENCODING_CSTR_LEN >= 127 { + panic!("encoding string was too long! The maximum supported length is 1023."); + } + + static_encoding_str_array(Self::ENCODING, NestingLevel::new()) + }; + + /// The encoding as a static [`CStr`]. + /// + /// This has the same output as `Encoding::to_string`, but it is created + /// at compile-time instead. + /// + /// The encoding is guaranteed to be a pure ASCII string. + #[cfg(feature = "std")] + const ENCODING_CSTR: &'static CStr = { + let mut slice: &[u8] = &Self::__ENCODING_CSTR_ARRAY; + // Cut down to desired size (length + 1 for NUL byte) + // Equivalent to: + // slice[0..Self::__ENCODING_CSTR_LEN + 1] + while slice.len() > Self::__ENCODING_CSTR_LEN + 1 { + if let Some(res) = slice.split_last() { + slice = res.1; + } else { + unreachable!(); + } + } + // SAFETY: `static_encoding_str_array` is guaranteed to not contain + // any NULL bytes (the only place those could appear would be in a + // struct or union name, and that case is checked). + unsafe { CStr::from_bytes_with_nul_unchecked(slice) } + }; } /// Types whoose references has an Objective-C type-encoding. @@ -736,4 +781,39 @@ mod tests { assert_eq!(<(i8,)>::ENCODINGS, &[i8::ENCODING]); assert_eq!(<(i8, u32)>::ENCODINGS, &[i8::ENCODING, u32::ENCODING]); } + + #[test] + #[cfg(feature = "std")] + fn test_cstr_simple() { + assert_eq!(i8::__ENCODING_CSTR_LEN, 1); + + let mut array = [0; 1024]; + array[0] = b'c'; + assert_eq!(i8::__ENCODING_CSTR_ARRAY, array); + + let cstr = CStr::from_bytes_with_nul(b"c\0").unwrap(); + assert_eq!(i8::ENCODING_CSTR, cstr); + } + + #[test] + #[cfg(feature = "std")] + fn test_cstr() { + struct X; + + unsafe impl Encode for X { + const ENCODING: Encoding<'static> = Encoding::Struct( + "abc", + &[ + Encoding::Union("def", &[Encoding::Char]), + <*const *const i8>::ENCODING, + >::ENCODING, + ::ENCODING, + ], + ); + } + + let s = b"{abc=(def=c)^*A^As^?}\0"; + + assert_eq!(X::ENCODING_CSTR.to_bytes_with_nul(), s); + } } diff --git a/objc2-encode/src/lib.rs b/objc2-encode/src/lib.rs index 5554843c7..1df96a888 100644 --- a/objc2-encode/src/lib.rs +++ b/objc2-encode/src/lib.rs @@ -110,9 +110,6 @@ mod encode; mod encoding; mod helper; mod parse; - -// Will be used at some point when generic constants are available -#[allow(dead_code)] mod static_str; pub use self::encode::{Encode, EncodeArguments, RefEncode}; diff --git a/test-assembly/crates/test_encoding_cstr/Cargo.toml b/test-assembly/crates/test_encoding_cstr/Cargo.toml new file mode 100644 index 000000000..7aaa9f726 --- /dev/null +++ b/test-assembly/crates/test_encoding_cstr/Cargo.toml @@ -0,0 +1,26 @@ +[package] +name = "test_encoding_cstr" +version = "0.1.0" +edition = "2021" +publish = false + +[lib] +path = "lib.rs" + +[dependencies] +objc2-encode = { path = "../../../objc2-encode", default-features = false } + +[features] +default = ["apple", "std"] +std = ["objc2-encode/std"] + +# Runtime +apple = [] +gnustep-1-7 = [] +gnustep-1-8 = ["gnustep-1-7"] +gnustep-1-9 = ["gnustep-1-8"] +gnustep-2-0 = ["gnustep-1-9"] +gnustep-2-1 = ["gnustep-2-0"] + +# Hack +assembly-features = ["std"] diff --git a/test-assembly/crates/test_encoding_cstr/expected/apple-aarch64.s b/test-assembly/crates/test_encoding_cstr/expected/apple-aarch64.s new file mode 100644 index 000000000..987168def --- /dev/null +++ b/test-assembly/crates/test_encoding_cstr/expected/apple-aarch64.s @@ -0,0 +1,13 @@ + .section __TEXT,__text,regular,pure_instructions + .section __TEXT,__const +l_anon.a88231c846af3b75605317c1ca346ede.0: + .asciz "c\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + + .section __DATA,__const + .globl _ENC + .p2align 3 +_ENC: + .quad l_anon.a88231c846af3b75605317c1ca346ede.0 + .asciz "\002\000\000\000\000\000\000" + +.subsections_via_symbols diff --git a/test-assembly/crates/test_encoding_cstr/expected/apple-armv7.s b/test-assembly/crates/test_encoding_cstr/expected/apple-armv7.s new file mode 100644 index 000000000..652d900da --- /dev/null +++ b/test-assembly/crates/test_encoding_cstr/expected/apple-armv7.s @@ -0,0 +1,14 @@ + .section __TEXT,__text,regular,pure_instructions + .syntax unified + .section __TEXT,__const +l_anon.a88231c846af3b75605317c1ca346ede.0: + .asciz "c\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + + .section __DATA,__const + .globl _ENC + .p2align 2 +_ENC: + .long l_anon.a88231c846af3b75605317c1ca346ede.0 + .asciz "\002\000\000" + +.subsections_via_symbols diff --git a/test-assembly/crates/test_encoding_cstr/expected/apple-armv7s.s b/test-assembly/crates/test_encoding_cstr/expected/apple-armv7s.s new file mode 100644 index 000000000..652d900da --- /dev/null +++ b/test-assembly/crates/test_encoding_cstr/expected/apple-armv7s.s @@ -0,0 +1,14 @@ + .section __TEXT,__text,regular,pure_instructions + .syntax unified + .section __TEXT,__const +l_anon.a88231c846af3b75605317c1ca346ede.0: + .asciz "c\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + + .section __DATA,__const + .globl _ENC + .p2align 2 +_ENC: + .long l_anon.a88231c846af3b75605317c1ca346ede.0 + .asciz "\002\000\000" + +.subsections_via_symbols diff --git a/test-assembly/crates/test_encoding_cstr/expected/apple-x86.s b/test-assembly/crates/test_encoding_cstr/expected/apple-x86.s new file mode 100644 index 000000000..ea253c6f6 --- /dev/null +++ b/test-assembly/crates/test_encoding_cstr/expected/apple-x86.s @@ -0,0 +1,14 @@ + .section __TEXT,__text,regular,pure_instructions + .intel_syntax noprefix + .section __TEXT,__const +l_anon.a88231c846af3b75605317c1ca346ede.0: + .asciz "c\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + + .section __DATA,__const + .globl _ENC + .p2align 2 +_ENC: + .long l_anon.a88231c846af3b75605317c1ca346ede.0 + .asciz "\002\000\000" + +.subsections_via_symbols diff --git a/test-assembly/crates/test_encoding_cstr/expected/apple-x86_64.s b/test-assembly/crates/test_encoding_cstr/expected/apple-x86_64.s new file mode 100644 index 000000000..a71ee045e --- /dev/null +++ b/test-assembly/crates/test_encoding_cstr/expected/apple-x86_64.s @@ -0,0 +1,14 @@ + .section __TEXT,__text,regular,pure_instructions + .intel_syntax noprefix + .section __TEXT,__const +l_anon.a88231c846af3b75605317c1ca346ede.0: + .asciz "c\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + + .section __DATA,__const + .globl _ENC + .p2align 3 +_ENC: + .quad l_anon.a88231c846af3b75605317c1ca346ede.0 + .asciz "\002\000\000\000\000\000\000" + +.subsections_via_symbols diff --git a/test-assembly/crates/test_encoding_cstr/expected/gnustep-x86.s b/test-assembly/crates/test_encoding_cstr/expected/gnustep-x86.s new file mode 100644 index 000000000..1505e57a7 --- /dev/null +++ b/test-assembly/crates/test_encoding_cstr/expected/gnustep-x86.s @@ -0,0 +1,18 @@ + .text + .intel_syntax noprefix + .type .Lanon.a88231c846af3b75605317c1ca346ede.0,@object + .section .rodata..Lanon.a88231c846af3b75605317c1ca346ede.0,"a",@progbits +.Lanon.a88231c846af3b75605317c1ca346ede.0: + .asciz "c\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + .size .Lanon.a88231c846af3b75605317c1ca346ede.0, 128 + + .type ENC,@object + .section .data.rel.ro.ENC,"aw",@progbits + .globl ENC + .p2align 2 +ENC: + .long .Lanon.a88231c846af3b75605317c1ca346ede.0 + .asciz "\002\000\000" + .size ENC, 8 + + .section ".note.GNU-stack","",@progbits diff --git a/test-assembly/crates/test_encoding_cstr/expected/gnustep-x86_64.s b/test-assembly/crates/test_encoding_cstr/expected/gnustep-x86_64.s new file mode 100644 index 000000000..63ad4915d --- /dev/null +++ b/test-assembly/crates/test_encoding_cstr/expected/gnustep-x86_64.s @@ -0,0 +1,18 @@ + .text + .intel_syntax noprefix + .type .Lanon.a88231c846af3b75605317c1ca346ede.0,@object + .section .rodata..Lanon.a88231c846af3b75605317c1ca346ede.0,"a",@progbits +.Lanon.a88231c846af3b75605317c1ca346ede.0: + .asciz "c\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + .size .Lanon.a88231c846af3b75605317c1ca346ede.0, 128 + + .type ENC,@object + .section .data.rel.ro.ENC,"aw",@progbits + .globl ENC + .p2align 3 +ENC: + .quad .Lanon.a88231c846af3b75605317c1ca346ede.0 + .asciz "\002\000\000\000\000\000\000" + .size ENC, 16 + + .section ".note.GNU-stack","",@progbits diff --git a/test-assembly/crates/test_encoding_cstr/lib.rs b/test-assembly/crates/test_encoding_cstr/lib.rs new file mode 100644 index 000000000..c74ede513 --- /dev/null +++ b/test-assembly/crates/test_encoding_cstr/lib.rs @@ -0,0 +1,7 @@ +//! Test that the encoding string that we output is not full length. +use std::ffi::CStr; + +use objc2_encode::Encode; + +#[no_mangle] +static ENC: &CStr = i8::ENCODING_CSTR; diff --git a/test-ui/src/main.rs b/test-ui/src/main.rs index 045bf91e3..82b27b7a1 100644 --- a/test-ui/src/main.rs +++ b/test-ui/src/main.rs @@ -14,10 +14,16 @@ #[cfg(feature = "run")] fn main() { let t = trybuild::TestCases::new(); + let path = std::path::Path::new(env!("CARGO_MANIFEST_DIR")) .join("ui") .join("*.rs"); t.compile_fail(path); + + let path = std::path::Path::new(env!("CARGO_MANIFEST_DIR")) + .join("ui-ignore") + .join("*.rs"); + t.pass(path); } #[cfg(not(feature = "run"))] diff --git a/test-ui/ui-ignore/encode_cstr_not_ident.rs b/test-ui/ui-ignore/encode_cstr_not_ident.rs new file mode 100644 index 000000000..0ea52d93d --- /dev/null +++ b/test-ui/ui-ignore/encode_cstr_not_ident.rs @@ -0,0 +1,15 @@ +//! Test that compilation fails when the struct name is invalid. +//! +//! Ideally, this should be tested by `trybuild`, but it doesn't work at the +//! moment (`cargo check` doesn't catch the error). +use objc2::{Encode, Encoding}; + +struct X; + +unsafe impl Encode for X { + const ENCODING: Encoding<'static> = Encoding::Struct("-", &[]); +} + +fn main() { + let _ = X::ENCODING_CSTR; +} diff --git a/test-ui/ui-ignore/encode_cstr_too_long.rs b/test-ui/ui-ignore/encode_cstr_too_long.rs new file mode 100644 index 000000000..08b610fcb --- /dev/null +++ b/test-ui/ui-ignore/encode_cstr_too_long.rs @@ -0,0 +1,17 @@ +//! Test that compilation fails when the encoding is too long. +//! +//! Ideally, this should be tested by `trybuild`, but it doesn't work at the +//! moment (`cargo check` doesn't catch the error). +use objc2::{Encode, Encoding}; + +struct X; + +const S: &str = unsafe { std::str::from_utf8_unchecked(&[b'a'; 1020]) }; + +unsafe impl Encode for X { + const ENCODING: Encoding<'static> = Encoding::Struct(S, &[]); +} + +fn main() { + let _ = X::ENCODING_CSTR; +} diff --git a/tests/src/test_encode_utils.rs b/tests/src/test_encode_utils.rs index 754655971..885ecb68c 100644 --- a/tests/src/test_encode_utils.rs +++ b/tests/src/test_encode_utils.rs @@ -20,6 +20,12 @@ unsafe fn assert_encoding(s: *const c_char, e: Encoding) { assert_eq!(e.to_string(), s.trim_start_matches('r')); } +unsafe fn assert_ty(s: *const c_char) { + assert_encoding(s, T::ENCODING); + // To ensure ENCODING_CSTR is implemented correctly + assert_eq!(T::ENCODING_CSTR.to_str().unwrap(), T::ENCODING.to_string()); +} + #[allow(unused)] unsafe fn assert_str(s: *const c_char, expected: T) { let s = CStr::from_ptr(s).to_str().unwrap(); @@ -39,6 +45,16 @@ macro_rules! assert_inner { unsafe { assert_encoding($stat, $expected) }; } }; + (ty $(#[$m:meta])* $stat:ident => $expected:ty) => { + $(#[$m])* + #[test] + fn $stat() { + extern "C" { + static $stat: *const c_char; + } + unsafe { assert_ty::<$expected>($stat) }; + } + }; (str $(#[$m:meta])* $stat:ident => $expected:expr) => { $(#[$m])* #[test] @@ -78,10 +94,10 @@ macro_rules! assert_types { $stat:ident $($should_atomic:ident)? => $type:ident ) => { paste! { - assert_inner!(enc $(#[$m])* [] => <$type>::ENCODING); - assert_inner!(enc $(#[$m])* [] => <*const $type>::ENCODING); - assert_inner!(enc $(#[$m])* [] => <*const *const $type>::ENCODING); - assert_inner!(enc $(#[$m])* [] => <*const *const *const $type>::ENCODING); + assert_inner!(ty $(#[$m])* [] => $type); + assert_inner!(ty $(#[$m])* [] => *const $type); + assert_inner!(ty $(#[$m])* [] => *const *const $type); + assert_inner!(ty $(#[$m])* [] => *const *const *const $type); $(assert_types!(#$should_atomic);)? assert_inner!(enc $(#[$m])* $(#[cfg($should_atomic)])? [] => Encoding::Atomic(&<$type>::ENCODING)); assert_inner!(enc $(#[$m])* $(#[cfg($should_atomic)])? [] => Encoding::Pointer(&Encoding::Atomic(&<$type>::ENCODING)));