real_c_string/
lib.rs

1extern crate proc_macro;
2
3use quote::quote;
4use syn::{
5	parse::{Parse, ParseStream, Result},
6	parse_macro_input,
7};
8
9/// Contains string parsed from tokens passed to proc macro
10struct RealCString {
11	string: String,
12}
13
14impl Parse for RealCString {
15	fn parse(input: ParseStream) -> Result<Self> {
16		if let syn::Lit::Str(str) = input.parse()? {
17			Ok(RealCString {
18				string: str.value(),
19			})
20		} else {
21			Err(input.error("expected Str instead of ByteStr"))
22		}
23	}
24}
25
26#[derive(Copy, Clone)]
27enum TransformType {
28	CString,
29	CWString,
30}
31
32impl TransformType {
33	/// Returns max character that can fit into this transform
34	fn max_char(&self) -> u32 {
35		match self {
36			Self::CString => 0xff,
37			Self::CWString => 0xffff,
38		}
39	}
40}
41
42/// Transforms passed string to needed form, used by proc macro at bottom
43fn transform(input: RealCString, transform_type: TransformType) -> proc_macro::TokenStream {
44	use TransformType::{CString, CWString};
45
46	let stream = {
47		let bytes: Vec<_> = input
48			.string
49			.chars()
50			.enumerate()
51			.map(|(offset, cur_char)| {
52				let out = if cur_char as u32 <= transform_type.max_char() {
53					cur_char
54				} else {
55					return quote! {
56						::core::compile_error!(
57							::core::concat!(
58								"Unsupported character \"", #cur_char, "\" at offset ", #offset
59							)
60						),
61					};
62				};
63				match transform_type {
64					CString => {
65						let res = out as i8;
66						quote! {#res,}
67					}
68					CWString => {
69						let res = out as i16;
70						quote! {#res,}
71					}
72				}
73			})
74			.collect();
75		match transform_type {
76			CString => quote! {
77				&[#(#bytes)* 0i8,] as *const i8
78			},
79			CWString => quote! {
80				&[#(#bytes)* 0i16,] as *const i16
81			},
82		}
83	};
84	proc_macro::TokenStream::from(stream)
85}
86
87/// Produces a C-string literal with the same representation as that of strings in C at ASM level
88/// Used in vmprotect crate, because vmprotect disassembles code and finds usages like this.
89///
90/// The result of this macro invocation is of type `*const i8`.
91///
92/// ```rust
93/// use real_c_string::real_c_string;
94/// assert_eq!(0i8, unsafe { *real_c_string!("") });
95///
96/// let c_string = real_c_string!("Hello world!");
97/// let same_as_array_of_bytes: [i8; 13] =
98///     [72i8, 101i8, 108i8, 108i8, 111i8, 32i8, 119i8, 111i8, 114i8, 108i8, 100i8, 33i8, 0i8];
99/// for i in 0..13 {
100///     assert_eq!(
101///         same_as_array_of_bytes[i],
102///         unsafe { *c_string.offset(i as isize) }
103///     );
104/// }
105/// ```
106#[proc_macro]
107pub fn real_c_string(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
108	transform(
109		parse_macro_input!(input as RealCString),
110		TransformType::CString,
111	)
112}
113
114/// Same as `real_c_string`, but used for wchar_t* strings
115///
116/// The result of this macro invocation is of type `*const i16`.
117///
118/// ```rust
119/// use real_c_string::real_c_wstring;
120/// assert_eq!(0i16, unsafe { *real_c_wstring!("") });
121///
122/// let c_wstring = real_c_wstring!("Hello world!");
123/// let same_as_array_of_bytes: [i16; 13] =
124///     [72i16, 101i16, 108i16, 108i16, 111i16, 32i16, 119i16, 111i16, 114i16, 108i16, 100i16, 33i16, 0i16];
125/// for i in 0..13 {
126///     assert_eq!(
127///         same_as_array_of_bytes[i],
128///         unsafe { *c_wstring.offset(i as isize) },
129///     );
130/// }
131///
132/// let c_wstring = real_c_wstring!("Привет world!");
133/// let same_as_array_of_bytes: [i16; 14]
134///     = [1055i16, 1088i16, 1080i16, 1074i16, 1077i16, 1090i16, 32i16, 119i16, 111i16, 114i16, 108i16, 100i16, 33i16, 0i16];
135/// for i in 0..13 {
136///     assert_eq!(
137///         same_as_array_of_bytes[i],
138///         unsafe { *c_wstring.offset(i as isize) },
139///     );
140/// }
141/// ```
142#[proc_macro]
143pub fn real_c_wstring(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
144	transform(
145		parse_macro_input!(input as RealCString),
146		TransformType::CWString,
147	)
148}