objrs_frameworks_foundation_macros/
lib.rs

1// The contents of this file is licensed by its authors and copyright holders under the Apache
2// License (Version 2.0), MIT license, or Mozilla Public License (Version 2.0), at your option. The
3// contents of this file may not be copied, modified, or distributed except according to those
4// terms. See the COPYRIGHT file at the top-level directory of this distribution for copies of these
5// licenses and more information.
6
7#![feature(proc_macro_diagnostic)]
8#![recursion_limit = "128"]
9
10extern crate core;
11extern crate proc_macro;
12extern crate proc_macro2;
13#[macro_use]
14extern crate quote;
15extern crate syn;
16
17use proc_macro2::Span;
18use syn::{parse2, punctuated::Punctuated, spanned::Spanned, token::Comma, LitByteStr, LitStr};
19
20// TODO: pull this out into a separate library that can be shared between this crate and /macros.
21#[link(name = "c")]
22extern "C" {
23  fn arc4random_buf(buf: *mut u8, nbytes: usize);
24}
25
26#[inline(always)]
27fn random<T>() -> T {
28  let mut buf = unsafe { core::mem::uninitialized() };
29  unsafe {
30    arc4random_buf(&mut buf as *mut _ as *mut u8, core::mem::size_of::<T>());
31  }
32  return buf;
33}
34
35#[inline]
36fn random_identifier() -> [u8; 32] {
37  let uuid = random::<[u64; 2]>();
38  let mask = 0x0f0f0f0f0f0f0f0f;
39  let aaaaaaaa = 0x6161616161616161;
40  let symbol: [u64; 4] = [
41    aaaaaaaa + (uuid[0] & mask),
42    aaaaaaaa + ((uuid[0] >> 4) & mask),
43    aaaaaaaa + (uuid[1] & mask),
44    aaaaaaaa + ((uuid[1] >> 4) & mask),
45  ];
46  return unsafe { core::mem::transmute(symbol) };
47}
48
49fn make_literal(mut value: String) -> proc_macro2::TokenStream {
50  let use_utf16 = value.bytes().any(|b| b == 0 || !b.is_ascii());
51  value.push('\x00');
52
53  let random_id = &random_identifier();
54  let random_id = unsafe { core::str::from_utf8_unchecked(random_id) };
55
56  let string_export_name = ["\x01L__unnamed_cfstring_.__objrs_str.", random_id].concat();
57
58  let bytes_link_section;
59  let info_flags;
60  let char_type;
61  let array_length;
62  let chars;
63  let bytes_export_name;
64  if use_utf16 {
65    bytes_link_section = "__TEXT,__ustring";
66    info_flags = 2000u32; // This value assume little endian.
67    char_type = quote!(u16);
68    let utf16: Punctuated<u16, Comma> = value.encode_utf16().collect();
69    array_length = utf16.len();
70    chars = quote!([#utf16]);
71    bytes_export_name = ["\x01l_.str.__objrs_str.", random_id].concat();
72  } else {
73    bytes_link_section = "__TEXT,__cstring,cstring_literals";
74    info_flags = 1992u32; // This value assume little endian.
75    char_type = quote!(u8);
76    let bytes = value.as_bytes();
77    array_length = bytes.len();
78    let bytes = LitByteStr::new(bytes, Span::call_site()); // TODO: use def_site().
79    chars = quote!(*#bytes);
80    bytes_export_name = ["\x01L_.str.__objrs_str.", random_id].concat();
81  }
82
83  return quote!{{
84      extern crate objrs_frameworks_foundation as __objrs_root;
85
86      #[link_section = #bytes_link_section]
87      #[export_name = #bytes_export_name]
88      #[doc(hidden)]
89      static BYTES: [__objrs_root::__objrs::#char_type; #array_length] = #chars;
90
91      #[link_section = "__DATA,__cfstring"]
92      #[export_name = #string_export_name]
93      #[doc(hidden)]
94      static STRING: __objrs_root::__objrs::CFConstantString = __objrs_root::__objrs::CFConstantString{
95          isa:    unsafe { &__objrs_root::__objrs::CFConstantStringClassReference },
96          info:   #info_flags,
97          ptr:    unsafe { __objrs_root::__objrs::TransmuteHack { from: &BYTES }.to },
98          length: #array_length - 1,
99      };
100
101      unsafe { __objrs_root::__objrs::TransmuteHack::<_, &'static __objrs_root::NSString> { from: &STRING }.to }
102  }};
103}
104
105#[proc_macro]
106pub fn nsstring(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
107  let input: proc_macro2::TokenStream = input.into();
108  let span = input.span();
109  match parse2::<LitStr>(input.into()) {
110    Ok(value) => return make_literal(value.value()).into(),
111    Err(err) => {
112      span.unstable().error(err.to_string()).note("nsstring! requires a single string literal. Examples: nsstring!(\"Hello, world!\"); nsstring!(\"こんにちは世界!\"); nsstring!(r#\"नमस्ते दुनिया!\"#);").emit();
113      // Return an empty NSString. This should help other diagnostic messages.
114      return make_literal(String::new()).into();
115    }
116  }
117}