Skip to content

Commit c5ad3ce

Browse files
committed
Add static encoding strings
1 parent fdbe88f commit c5ad3ce

File tree

16 files changed

+279
-7
lines changed

16 files changed

+279
-7
lines changed

objc2-encode/CHANGELOG.md

+3
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
66

77
## Unreleased - YYYY-MM-DD
88

9+
### Added
10+
* Added `Encode::ENCODING_CSTR` for statically generating an encoding string.
11+
912
### Fixed
1013
* Fixed the encoding output and comparison of structs behind pointers.
1114

objc2-encode/src/encode.rs

+80
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,11 @@ use core::num::{
66
};
77
use core::ptr::NonNull;
88
use core::sync::atomic;
9+
#[cfg(feature = "std")] // TODO: Use `core`
10+
use std::ffi::CStr;
911

12+
use crate::helper::NestingLevel;
13+
use crate::static_str::{static_encoding_str_array, static_encoding_str_len};
1014
use crate::Encoding;
1115

1216
/// Types that have an Objective-C type-encoding.
@@ -16,6 +20,7 @@ use crate::Encoding;
1620
/// If your type is an opaque type you should not need to implement this;
1721
/// there you will only need [`RefEncode`].
1822
///
23+
///
1924
/// # Safety
2025
///
2126
/// The type must be FFI-safe, meaning a C-compatible `repr` (`repr(C)`,
@@ -33,6 +38,9 @@ use crate::Encoding;
3338
/// passed to Objective-C via. `objc2::msg_send!` their destructor will not be
3439
/// called!
3540
///
41+
/// Finally, you must not override [`ENCODING_CSTR`][Self::ENCODING_CSTR].
42+
///
43+
///
3644
/// # Examples
3745
///
3846
/// Implementing for a struct:
@@ -69,6 +77,43 @@ use crate::Encoding;
6977
pub unsafe trait Encode {
7078
/// The Objective-C type-encoding for this type.
7179
const ENCODING: Encoding<'static>;
80+
81+
#[doc(hidden)]
82+
const __ENCODING_CSTR_LEN: usize = static_encoding_str_len(Self::ENCODING, NestingLevel::new());
83+
84+
#[doc(hidden)]
85+
const __ENCODING_CSTR_ARRAY: [u8; 128] = {
86+
if Self::__ENCODING_CSTR_LEN >= 127 {
87+
panic!("encoding string was too long! The maximum supported length is 1023.");
88+
}
89+
90+
static_encoding_str_array(Self::ENCODING, NestingLevel::new())
91+
};
92+
93+
/// The encoding as a static [`CStr`].
94+
///
95+
/// This has the same output as `Encoding::to_string`, but it is created
96+
/// at compile-time instead.
97+
///
98+
/// The encoding is guaranteed to be a pure ASCII string.
99+
#[cfg(feature = "std")]
100+
const ENCODING_CSTR: &'static CStr = {
101+
let mut slice: &[u8] = &Self::__ENCODING_CSTR_ARRAY;
102+
// Cut down to desired size (length + 1 for NUL byte)
103+
// Equivalent to:
104+
// slice[0..Self::__ENCODING_CSTR_LEN + 1]
105+
while slice.len() > Self::__ENCODING_CSTR_LEN + 1 {
106+
if let Some(res) = slice.split_last() {
107+
slice = res.1;
108+
} else {
109+
unreachable!();
110+
}
111+
}
112+
// SAFETY: `static_encoding_str_array` is guaranteed to not contain
113+
// any NULL bytes (the only place those could appear would be in a
114+
// struct or union name, and that case is checked).
115+
unsafe { CStr::from_bytes_with_nul_unchecked(slice) }
116+
};
72117
}
73118

74119
/// Types whoose references has an Objective-C type-encoding.
@@ -736,4 +781,39 @@ mod tests {
736781
assert_eq!(<(i8,)>::ENCODINGS, &[i8::ENCODING]);
737782
assert_eq!(<(i8, u32)>::ENCODINGS, &[i8::ENCODING, u32::ENCODING]);
738783
}
784+
785+
#[test]
786+
#[cfg(feature = "std")]
787+
fn test_cstr_simple() {
788+
assert_eq!(i8::__ENCODING_CSTR_LEN, 1);
789+
790+
let mut array = [0; 1024];
791+
array[0] = b'c';
792+
assert_eq!(i8::__ENCODING_CSTR_ARRAY, array);
793+
794+
let cstr = CStr::from_bytes_with_nul(b"c\0").unwrap();
795+
assert_eq!(i8::ENCODING_CSTR, cstr);
796+
}
797+
798+
#[test]
799+
#[cfg(feature = "std")]
800+
fn test_cstr() {
801+
struct X;
802+
803+
unsafe impl Encode for X {
804+
const ENCODING: Encoding<'static> = Encoding::Struct(
805+
"abc",
806+
&[
807+
Encoding::Union("def", &[Encoding::Char]),
808+
<*const *const i8>::ENCODING,
809+
<AtomicPtr<AtomicI16>>::ENCODING,
810+
<extern "C" fn()>::ENCODING,
811+
],
812+
);
813+
}
814+
815+
let s = b"{abc=(def=c)^*A^As^?}\0";
816+
817+
assert_eq!(X::ENCODING_CSTR.to_bytes_with_nul(), s);
818+
}
739819
}

objc2-encode/src/lib.rs

-3
Original file line numberDiff line numberDiff line change
@@ -110,9 +110,6 @@ mod encode;
110110
mod encoding;
111111
mod helper;
112112
mod parse;
113-
114-
// Will be used at some point when generic constants are available
115-
#[allow(dead_code)]
116113
mod static_str;
117114

118115
pub use self::encode::{Encode, EncodeArguments, RefEncode};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
[package]
2+
name = "test_encoding_cstr"
3+
version = "0.1.0"
4+
edition = "2021"
5+
publish = false
6+
7+
[lib]
8+
path = "lib.rs"
9+
10+
[dependencies]
11+
objc2-encode = { path = "../../../objc2-encode", default-features = false }
12+
13+
[features]
14+
default = ["apple", "std"]
15+
std = ["objc2-encode/std"]
16+
17+
# Runtime
18+
apple = []
19+
gnustep-1-7 = []
20+
gnustep-1-8 = ["gnustep-1-7"]
21+
gnustep-1-9 = ["gnustep-1-8"]
22+
gnustep-2-0 = ["gnustep-1-9"]
23+
gnustep-2-1 = ["gnustep-2-0"]
24+
25+
# Hack
26+
assembly-features = ["std"]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
.section __TEXT,__text,regular,pure_instructions
2+
.section __TEXT,__const
3+
l_anon.a88231c846af3b75605317c1ca346ede.0:
4+
.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"
5+
6+
.section __DATA,__const
7+
.globl _ENC
8+
.p2align 3
9+
_ENC:
10+
.quad l_anon.a88231c846af3b75605317c1ca346ede.0
11+
.asciz "\002\000\000\000\000\000\000"
12+
13+
.subsections_via_symbols
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
.section __TEXT,__text,regular,pure_instructions
2+
.syntax unified
3+
.section __TEXT,__const
4+
l_anon.a88231c846af3b75605317c1ca346ede.0:
5+
.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"
6+
7+
.section __DATA,__const
8+
.globl _ENC
9+
.p2align 2
10+
_ENC:
11+
.long l_anon.a88231c846af3b75605317c1ca346ede.0
12+
.asciz "\002\000\000"
13+
14+
.subsections_via_symbols
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
.section __TEXT,__text,regular,pure_instructions
2+
.syntax unified
3+
.section __TEXT,__const
4+
l_anon.a88231c846af3b75605317c1ca346ede.0:
5+
.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"
6+
7+
.section __DATA,__const
8+
.globl _ENC
9+
.p2align 2
10+
_ENC:
11+
.long l_anon.a88231c846af3b75605317c1ca346ede.0
12+
.asciz "\002\000\000"
13+
14+
.subsections_via_symbols
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
.section __TEXT,__text,regular,pure_instructions
2+
.intel_syntax noprefix
3+
.section __TEXT,__const
4+
l_anon.a88231c846af3b75605317c1ca346ede.0:
5+
.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"
6+
7+
.section __DATA,__const
8+
.globl _ENC
9+
.p2align 2
10+
_ENC:
11+
.long l_anon.a88231c846af3b75605317c1ca346ede.0
12+
.asciz "\002\000\000"
13+
14+
.subsections_via_symbols
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
.section __TEXT,__text,regular,pure_instructions
2+
.intel_syntax noprefix
3+
.section __TEXT,__const
4+
l_anon.a88231c846af3b75605317c1ca346ede.0:
5+
.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"
6+
7+
.section __DATA,__const
8+
.globl _ENC
9+
.p2align 3
10+
_ENC:
11+
.quad l_anon.a88231c846af3b75605317c1ca346ede.0
12+
.asciz "\002\000\000\000\000\000\000"
13+
14+
.subsections_via_symbols
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
.text
2+
.intel_syntax noprefix
3+
.type .Lanon.a88231c846af3b75605317c1ca346ede.0,@object
4+
.section .rodata..Lanon.a88231c846af3b75605317c1ca346ede.0,"a",@progbits
5+
.Lanon.a88231c846af3b75605317c1ca346ede.0:
6+
.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"
7+
.size .Lanon.a88231c846af3b75605317c1ca346ede.0, 128
8+
9+
.type ENC,@object
10+
.section .data.rel.ro.ENC,"aw",@progbits
11+
.globl ENC
12+
.p2align 2
13+
ENC:
14+
.long .Lanon.a88231c846af3b75605317c1ca346ede.0
15+
.asciz "\002\000\000"
16+
.size ENC, 8
17+
18+
.section ".note.GNU-stack","",@progbits
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
.text
2+
.intel_syntax noprefix
3+
.type .Lanon.a88231c846af3b75605317c1ca346ede.0,@object
4+
.section .rodata..Lanon.a88231c846af3b75605317c1ca346ede.0,"a",@progbits
5+
.Lanon.a88231c846af3b75605317c1ca346ede.0:
6+
.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"
7+
.size .Lanon.a88231c846af3b75605317c1ca346ede.0, 128
8+
9+
.type ENC,@object
10+
.section .data.rel.ro.ENC,"aw",@progbits
11+
.globl ENC
12+
.p2align 3
13+
ENC:
14+
.quad .Lanon.a88231c846af3b75605317c1ca346ede.0
15+
.asciz "\002\000\000\000\000\000\000"
16+
.size ENC, 16
17+
18+
.section ".note.GNU-stack","",@progbits
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
//! Test that the encoding string that we output is not full length.
2+
use std::ffi::CStr;
3+
4+
use objc2_encode::Encode;
5+
6+
#[no_mangle]
7+
static ENC: &CStr = i8::ENCODING_CSTR;

test-ui/src/main.rs

+6
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,16 @@
1414
#[cfg(feature = "run")]
1515
fn main() {
1616
let t = trybuild::TestCases::new();
17+
1718
let path = std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
1819
.join("ui")
1920
.join("*.rs");
2021
t.compile_fail(path);
22+
23+
let path = std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
24+
.join("ui-ignore")
25+
.join("*.rs");
26+
t.pass(path);
2127
}
2228

2329
#[cfg(not(feature = "run"))]
+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
//! Test that compilation fails when the struct name is invalid.
2+
//!
3+
//! Ideally, this should be tested by `trybuild`, but it doesn't work at the
4+
//! moment (`cargo check` doesn't catch the error).
5+
use objc2::{Encode, Encoding};
6+
7+
struct X;
8+
9+
unsafe impl Encode for X {
10+
const ENCODING: Encoding<'static> = Encoding::Struct("-", &[]);
11+
}
12+
13+
fn main() {
14+
let _ = X::ENCODING_CSTR;
15+
}
+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
//! Test that compilation fails when the encoding is too long.
2+
//!
3+
//! Ideally, this should be tested by `trybuild`, but it doesn't work at the
4+
//! moment (`cargo check` doesn't catch the error).
5+
use objc2::{Encode, Encoding};
6+
7+
struct X;
8+
9+
const S: &str = unsafe { std::str::from_utf8_unchecked(&[b'a'; 1020]) };
10+
11+
unsafe impl Encode for X {
12+
const ENCODING: Encoding<'static> = Encoding::Struct(S, &[]);
13+
}
14+
15+
fn main() {
16+
let _ = X::ENCODING_CSTR;
17+
}

tests/src/test_encode_utils.rs

+20-4
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,12 @@ unsafe fn assert_encoding(s: *const c_char, e: Encoding) {
2020
assert_eq!(e.to_string(), s.trim_start_matches('r'));
2121
}
2222

23+
unsafe fn assert_ty<T: Encode>(s: *const c_char) {
24+
assert_encoding(s, T::ENCODING);
25+
// To ensure ENCODING_CSTR is implemented correctly
26+
assert_eq!(T::ENCODING_CSTR.to_str().unwrap(), T::ENCODING.to_string());
27+
}
28+
2329
#[allow(unused)]
2430
unsafe fn assert_str<T: Display>(s: *const c_char, expected: T) {
2531
let s = CStr::from_ptr(s).to_str().unwrap();
@@ -39,6 +45,16 @@ macro_rules! assert_inner {
3945
unsafe { assert_encoding($stat, $expected) };
4046
}
4147
};
48+
(ty $(#[$m:meta])* $stat:ident => $expected:ty) => {
49+
$(#[$m])*
50+
#[test]
51+
fn $stat() {
52+
extern "C" {
53+
static $stat: *const c_char;
54+
}
55+
unsafe { assert_ty::<$expected>($stat) };
56+
}
57+
};
4258
(str $(#[$m:meta])* $stat:ident => $expected:expr) => {
4359
$(#[$m])*
4460
#[test]
@@ -78,10 +94,10 @@ macro_rules! assert_types {
7894
$stat:ident $($should_atomic:ident)? => $type:ident
7995
) => {
8096
paste! {
81-
assert_inner!(enc $(#[$m])* [<ENCODING_ $stat>] => <$type>::ENCODING);
82-
assert_inner!(enc $(#[$m])* [<ENCODING_ $stat _POINTER>] => <*const $type>::ENCODING);
83-
assert_inner!(enc $(#[$m])* [<ENCODING_ $stat _POINTER_POINTER>] => <*const *const $type>::ENCODING);
84-
assert_inner!(enc $(#[$m])* [<ENCODING_ $stat _POINTER_POINTER_POINTER>] => <*const *const *const $type>::ENCODING);
97+
assert_inner!(ty $(#[$m])* [<ENCODING_ $stat>] => $type);
98+
assert_inner!(ty $(#[$m])* [<ENCODING_ $stat _POINTER>] => *const $type);
99+
assert_inner!(ty $(#[$m])* [<ENCODING_ $stat _POINTER_POINTER>] => *const *const $type);
100+
assert_inner!(ty $(#[$m])* [<ENCODING_ $stat _POINTER_POINTER_POINTER>] => *const *const *const $type);
85101
$(assert_types!(#$should_atomic);)?
86102
assert_inner!(enc $(#[$m])* $(#[cfg($should_atomic)])? [<ENCODING_ $stat _ATOMIC>] => Encoding::Atomic(&<$type>::ENCODING));
87103
assert_inner!(enc $(#[$m])* $(#[cfg($should_atomic)])? [<ENCODING_ $stat _ATOMIC_POINTER>] => Encoding::Pointer(&Encoding::Atomic(&<$type>::ENCODING)));

0 commit comments

Comments
 (0)