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}