1 // SPDX-License-Identifier: Apache-2.0
2 
3 //================================================
4 // Macros
5 //================================================
6 
7 #[cfg(feature = "runtime")]
8 macro_rules! link {
9     (
10         @LOAD:
11         $(#[doc=$doc:expr])*
12         #[cfg($cfg:meta)]
13         fn $name:ident($($pname:ident: $pty:ty), *) $(-> $ret:ty)*
14     ) => (
15         $(#[doc=$doc])*
16         #[cfg($cfg)]
17         pub fn $name(library: &mut super::SharedLibrary) {
18             let symbol = unsafe { library.library.get(stringify!($name).as_bytes()) }.ok();
19             library.functions.$name = match symbol {
20                 Some(s) => *s,
21                 None => None,
22             };
23         }
24 
25         #[cfg(not($cfg))]
26         pub fn $name(_: &mut super::SharedLibrary) {}
27     );
28 
29     (
30         @LOAD:
31         fn $name:ident($($pname:ident: $pty:ty), *) $(-> $ret:ty)*
32     ) => (
33         link!(@LOAD: #[cfg(feature = "runtime")] fn $name($($pname: $pty), *) $(-> $ret)*);
34     );
35 
36     (
37         $(
38             $(#[doc=$doc:expr] #[cfg($cfg:meta)])*
39             pub fn $name:ident($($pname:ident: $pty:ty), *) $(-> $ret:ty)*;
40         )+
41     ) => (
42         use std::cell::{RefCell};
43         use std::fmt;
44         use std::sync::{Arc};
45         use std::path::{Path, PathBuf};
46 
47         /// The (minimum) version of a `libclang` shared library.
48         #[allow(missing_docs)]
49         #[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
50         pub enum Version {
51             V3_5 = 35,
52             V3_6 = 36,
53             V3_7 = 37,
54             V3_8 = 38,
55             V3_9 = 39,
56             V4_0 = 40,
57             V5_0 = 50,
58             V6_0 = 60,
59             V7_0 = 70,
60             V8_0 = 80,
61             V9_0 = 90,
62             V11_0 = 110,
63             V12_0 = 120,
64             V16_0 = 160,
65             V17_0 = 170,
66         }
67 
68         impl fmt::Display for Version {
69             fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
70                 use Version::*;
71                 match self {
72                     V3_5 => write!(f, "3.5.x"),
73                     V3_6 => write!(f, "3.6.x"),
74                     V3_7 => write!(f, "3.7.x"),
75                     V3_8 => write!(f, "3.8.x"),
76                     V3_9 => write!(f, "3.9.x"),
77                     V4_0 => write!(f, "4.0.x"),
78                     V5_0 => write!(f, "5.0.x"),
79                     V6_0 => write!(f, "6.0.x"),
80                     V7_0 => write!(f, "7.0.x"),
81                     V8_0 => write!(f, "8.0.x"),
82                     V9_0 => write!(f, "9.0.x - 10.0.x"),
83                     V11_0 => write!(f, "11.0.x"),
84                     V12_0 => write!(f, "12.0.x - 15.0.x"),
85                     V16_0 => write!(f, "16.0.x"),
86                     V17_0 => write!(f, "17.0.x or later"),
87                 }
88             }
89         }
90 
91         /// The set of functions loaded dynamically.
92         #[derive(Debug, Default)]
93         pub struct Functions {
94             $(
95                 $(#[doc=$doc] #[cfg($cfg)])*
96                 pub $name: Option<unsafe extern fn($($pname: $pty), *) $(-> $ret)*>,
97             )+
98         }
99 
100         /// A dynamically loaded instance of the `libclang` library.
101         #[derive(Debug)]
102         pub struct SharedLibrary {
103             library: libloading::Library,
104             path: PathBuf,
105             pub functions: Functions,
106         }
107 
108         impl SharedLibrary {
109             fn new(library: libloading::Library, path: PathBuf) -> Self {
110                 Self { library, path, functions: Functions::default() }
111             }
112 
113             /// Returns the path to this `libclang` shared library.
114             pub fn path(&self) -> &Path {
115                 &self.path
116             }
117 
118             /// Returns the (minimum) version of this `libclang` shared library.
119             ///
120             /// If this returns `None`, it indicates that the version is too old
121             /// to be supported by this crate (i.e., `3.4` or earlier). If the
122             /// version of this shared library is more recent than that fully
123             /// supported by this crate, the most recent fully supported version
124             /// will be returned.
125             pub fn version(&self) -> Option<Version> {
126                 macro_rules! check {
127                     ($fn:expr, $version:ident) => {
128                         if self.library.get::<unsafe extern fn()>($fn).is_ok() {
129                             return Some(Version::$version);
130                         }
131                     };
132                 }
133 
134                 unsafe {
135                     check!(b"clang_CXXMethod_isExplicit", V17_0);
136                     check!(b"clang_CXXMethod_isCopyAssignmentOperator", V16_0);
137                     check!(b"clang_Cursor_getVarDeclInitializer", V12_0);
138                     check!(b"clang_Type_getValueType", V11_0);
139                     check!(b"clang_Cursor_isAnonymousRecordDecl", V9_0);
140                     check!(b"clang_Cursor_getObjCPropertyGetterName", V8_0);
141                     check!(b"clang_File_tryGetRealPathName", V7_0);
142                     check!(b"clang_CXIndex_setInvocationEmissionPathOption", V6_0);
143                     check!(b"clang_Cursor_isExternalSymbol", V5_0);
144                     check!(b"clang_EvalResult_getAsLongLong", V4_0);
145                     check!(b"clang_CXXConstructor_isConvertingConstructor", V3_9);
146                     check!(b"clang_CXXField_isMutable", V3_8);
147                     check!(b"clang_Cursor_getOffsetOfField", V3_7);
148                     check!(b"clang_Cursor_getStorageClass", V3_6);
149                     check!(b"clang_Type_getNumTemplateArguments", V3_5);
150                 }
151 
152                 None
153             }
154         }
155 
156         thread_local!(static LIBRARY: RefCell<Option<Arc<SharedLibrary>>> = RefCell::new(None));
157 
158         /// Returns whether a `libclang` shared library is loaded on this thread.
159         pub fn is_loaded() -> bool {
160             LIBRARY.with(|l| l.borrow().is_some())
161         }
162 
163         fn with_library<T, F>(f: F) -> Option<T> where F: FnOnce(&SharedLibrary) -> T {
164             LIBRARY.with(|l| {
165                 match l.borrow().as_ref() {
166                     Some(library) => Some(f(&library)),
167                     _ => None,
168                 }
169             })
170         }
171 
172         $(
173             #[cfg_attr(feature="cargo-clippy", allow(clippy::missing_safety_doc))]
174             #[cfg_attr(feature="cargo-clippy", allow(clippy::too_many_arguments))]
175             $(#[doc=$doc] #[cfg($cfg)])*
176             pub unsafe fn $name($($pname: $pty), *) $(-> $ret)* {
177                 let f = with_library(|library| {
178                     if let Some(function) = library.functions.$name {
179                         function
180                     } else {
181                         panic!(
182                             r#"
183 A `libclang` function was called that is not supported by the loaded `libclang` instance.
184 
185     called function = `{0}`
186     loaded `libclang` instance = {1}
187 
188 This crate only supports `libclang` 3.5 and later.
189 The minimum `libclang` requirement for this particular function can be found here:
190 https://docs.rs/clang-sys/latest/clang_sys/{0}/index.html
191 
192 Instructions for installing `libclang` can be found here:
193 https://rust-lang.github.io/rust-bindgen/requirements.html
194 "#,
195                             stringify!($name),
196                             library
197                                 .version()
198                                 .map(|v| format!("{}", v))
199                                 .unwrap_or_else(|| "unsupported version".into()),
200                         );
201                     }
202                 }).expect("a `libclang` shared library is not loaded on this thread");
203                 f($($pname), *)
204             }
205 
206             $(#[doc=$doc] #[cfg($cfg)])*
207             pub mod $name {
208                 pub fn is_loaded() -> bool {
209                     super::with_library(|l| l.functions.$name.is_some()).unwrap_or(false)
210                 }
211             }
212         )+
213 
214         mod load {
215             $(link!(@LOAD: $(#[cfg($cfg)])* fn $name($($pname: $pty), *) $(-> $ret)*);)+
216         }
217 
218         /// Loads a `libclang` shared library and returns the library instance.
219         ///
220         /// This function does not attempt to load any functions from the shared library. The caller
221         /// is responsible for loading the functions they require.
222         ///
223         /// # Failures
224         ///
225         /// * a `libclang` shared library could not be found
226         /// * the `libclang` shared library could not be opened
227         pub fn load_manually() -> Result<SharedLibrary, String> {
228             #[allow(dead_code)]
229             mod build {
230                 include!(concat!(env!("OUT_DIR"), "/macros.rs"));
231                 pub mod common { include!(concat!(env!("OUT_DIR"), "/common.rs")); }
232                 pub mod dynamic { include!(concat!(env!("OUT_DIR"), "/dynamic.rs")); }
233             }
234 
235             let (directory, filename) = build::dynamic::find(true)?;
236             let path = directory.join(filename);
237 
238             unsafe {
239                 let library = libloading::Library::new(&path).map_err(|e| {
240                     format!(
241                         "the `libclang` shared library at {} could not be opened: {}",
242                         path.display(),
243                         e,
244                     )
245                 });
246 
247                 let mut library = SharedLibrary::new(library?, path);
248                 $(load::$name(&mut library);)+
249                 Ok(library)
250             }
251         }
252 
253         /// Loads a `libclang` shared library for use in the current thread.
254         ///
255         /// This functions attempts to load all the functions in the shared library. Whether a
256         /// function has been loaded can be tested by calling the `is_loaded` function on the
257         /// module with the same name as the function (e.g., `clang_createIndex::is_loaded()` for
258         /// the `clang_createIndex` function).
259         ///
260         /// # Failures
261         ///
262         /// * a `libclang` shared library could not be found
263         /// * the `libclang` shared library could not be opened
264         #[allow(dead_code)]
265         pub fn load() -> Result<(), String> {
266             let library = Arc::new(load_manually()?);
267             LIBRARY.with(|l| *l.borrow_mut() = Some(library));
268             Ok(())
269         }
270 
271         /// Unloads the `libclang` shared library in use in the current thread.
272         ///
273         /// # Failures
274         ///
275         /// * a `libclang` shared library is not in use in the current thread
276         pub fn unload() -> Result<(), String> {
277             let library = set_library(None);
278             if library.is_some() {
279                 Ok(())
280             } else {
281                 Err("a `libclang` shared library is not in use in the current thread".into())
282             }
283         }
284 
285         /// Returns the library instance stored in TLS.
286         ///
287         /// This functions allows for sharing library instances between threads.
288         pub fn get_library() -> Option<Arc<SharedLibrary>> {
289             LIBRARY.with(|l| l.borrow_mut().clone())
290         }
291 
292         /// Sets the library instance stored in TLS and returns the previous library.
293         ///
294         /// This functions allows for sharing library instances between threads.
295         pub fn set_library(library: Option<Arc<SharedLibrary>>) -> Option<Arc<SharedLibrary>> {
296             LIBRARY.with(|l| mem::replace(&mut *l.borrow_mut(), library))
297         }
298     )
299 }
300 
301 #[cfg(not(feature = "runtime"))]
302 macro_rules! link {
303     (
304         $(
305             $(#[doc=$doc:expr] #[cfg($cfg:meta)])*
306             pub fn $name:ident($($pname:ident: $pty:ty), *) $(-> $ret:ty)*;
307         )+
308     ) => (
309         extern {
310             $(
311                 $(#[doc=$doc] #[cfg($cfg)])*
312                 pub fn $name($($pname: $pty), *) $(-> $ret)*;
313             )+
314         }
315 
316         $(
317             $(#[doc=$doc] #[cfg($cfg)])*
318             pub mod $name {
319                 pub fn is_loaded() -> bool { true }
320             }
321         )+
322     )
323 }
324