context.rs 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305
  1. use anyhow::{anyhow, Result};
  2. use std::collections::BTreeMap;
  3. use std::path::{Path, PathBuf};
  4. use syn::parse::{Error as ParseError, Result as ParseResult};
  5. use syn::{Ident, ImplItem, ImplItemConst, Type, TypePath};
  6. /// Crate parse context
  7. ///
  8. /// Keeps track of modules defined within a crate.
  9. pub struct CrateContext {
  10. modules: BTreeMap<String, ParsedModule>,
  11. }
  12. impl CrateContext {
  13. pub fn parse(root: impl AsRef<Path>) -> Result<Self> {
  14. Ok(CrateContext {
  15. modules: ParsedModule::parse_recursive(root.as_ref())?,
  16. })
  17. }
  18. pub fn consts(&self) -> impl Iterator<Item = &syn::ItemConst> {
  19. self.modules.iter().flat_map(|(_, ctx)| ctx.consts())
  20. }
  21. pub fn impl_consts(&self) -> impl Iterator<Item = (&Ident, &syn::ImplItemConst)> {
  22. self.modules.iter().flat_map(|(_, ctx)| ctx.impl_consts())
  23. }
  24. pub fn structs(&self) -> impl Iterator<Item = &syn::ItemStruct> {
  25. self.modules.iter().flat_map(|(_, ctx)| ctx.structs())
  26. }
  27. pub fn enums(&self) -> impl Iterator<Item = &syn::ItemEnum> {
  28. self.modules.iter().flat_map(|(_, ctx)| ctx.enums())
  29. }
  30. pub fn type_aliases(&self) -> impl Iterator<Item = &syn::ItemType> {
  31. self.modules.iter().flat_map(|(_, ctx)| ctx.type_aliases())
  32. }
  33. pub fn modules(&self) -> impl Iterator<Item = ModuleContext<'_>> {
  34. self.modules.values().map(|detail| ModuleContext { detail })
  35. }
  36. pub fn root_module(&self) -> ModuleContext<'_> {
  37. ModuleContext {
  38. detail: self.modules.get("crate").unwrap(),
  39. }
  40. }
  41. // Perform Anchor safety checks on the parsed create
  42. pub fn safety_checks(&self) -> Result<()> {
  43. // Check all structs for unsafe field types, i.e. AccountInfo and UncheckedAccount.
  44. for ctx in self.modules.values() {
  45. for unsafe_field in ctx.unsafe_struct_fields() {
  46. // Check if unsafe field type has been documented with a /// SAFETY: doc string.
  47. let is_documented = unsafe_field.attrs.iter().any(|attr| {
  48. attr.tokens.clone().into_iter().any(|token| match token {
  49. // Check for doc comments containing CHECK
  50. proc_macro2::TokenTree::Literal(s) => s.to_string().contains("CHECK"),
  51. _ => false,
  52. })
  53. });
  54. if !is_documented {
  55. let ident = unsafe_field.ident.as_ref().unwrap();
  56. let span = ident.span();
  57. // Error if undocumented.
  58. return Err(anyhow!(
  59. r#"
  60. {}:{}:{}
  61. Struct field "{}" is unsafe, but is not documented.
  62. Please add a `/// CHECK:` doc comment explaining why no checks through types are necessary.
  63. Alternatively, for reasons like quick prototyping, you may disable the safety checks
  64. by using the `skip-lint` option.
  65. See https://www.anchor-lang.com/docs/the-accounts-struct#safety-checks for more information.
  66. "#,
  67. ctx.file.canonicalize().unwrap().display(),
  68. span.start().line,
  69. span.start().column,
  70. ident.to_string()
  71. ));
  72. };
  73. }
  74. }
  75. Ok(())
  76. }
  77. }
  78. /// Module parse context
  79. ///
  80. /// Keeps track of items defined within a module.
  81. #[derive(Copy, Clone)]
  82. pub struct ModuleContext<'krate> {
  83. detail: &'krate ParsedModule,
  84. }
  85. impl ModuleContext<'_> {
  86. pub fn items(&self) -> impl Iterator<Item = &syn::Item> {
  87. self.detail.items.iter()
  88. }
  89. }
  90. struct ParsedModule {
  91. name: String,
  92. file: PathBuf,
  93. path: String,
  94. items: Vec<syn::Item>,
  95. }
  96. struct UnparsedModule {
  97. file: PathBuf,
  98. path: String,
  99. name: String,
  100. item: syn::ItemMod,
  101. }
  102. impl ParsedModule {
  103. fn parse_recursive(root: &Path) -> Result<BTreeMap<String, ParsedModule>> {
  104. let mut modules = BTreeMap::new();
  105. let root_content = std::fs::read_to_string(root)?;
  106. let root_file = syn::parse_file(&root_content)?;
  107. let root_mod = Self::new(
  108. String::new(),
  109. root.to_owned(),
  110. "crate".to_owned(),
  111. root_file.items,
  112. );
  113. let mut unparsed = root_mod.unparsed_submodules();
  114. while let Some(to_parse) = unparsed.pop() {
  115. let path = format!("{}::{}", to_parse.path, to_parse.name);
  116. let module = Self::from_item_mod(&to_parse.file, &path, to_parse.item)?;
  117. unparsed.extend(module.unparsed_submodules());
  118. modules.insert(format!("{}{}", module.path, to_parse.name), module);
  119. }
  120. modules.insert(root_mod.name.clone(), root_mod);
  121. Ok(modules)
  122. }
  123. fn from_item_mod(
  124. parent_file: &Path,
  125. parent_path: &str,
  126. item: syn::ItemMod,
  127. ) -> ParseResult<Self> {
  128. Ok(match item.content {
  129. Some((_, items)) => {
  130. // The module content is within the parent file being parsed
  131. Self::new(
  132. parent_path.to_owned(),
  133. parent_file.to_owned(),
  134. item.ident.to_string(),
  135. items,
  136. )
  137. }
  138. None => {
  139. // The module is referencing some other file, so we need to load that
  140. // to parse the items it has.
  141. let parent_dir = parent_file.parent().unwrap();
  142. let parent_filename = parent_file.file_stem().unwrap().to_str().unwrap();
  143. let parent_mod_dir = parent_dir.join(parent_filename);
  144. let possible_file_paths = vec![
  145. parent_dir.join(format!("{}.rs", item.ident)),
  146. parent_dir.join(format!("{}/mod.rs", item.ident)),
  147. parent_mod_dir.join(format!("{}.rs", item.ident)),
  148. parent_mod_dir.join(format!("{}/mod.rs", item.ident)),
  149. ];
  150. let mod_file_path = possible_file_paths
  151. .into_iter()
  152. .find(|p| p.exists())
  153. .ok_or_else(|| ParseError::new_spanned(&item, "could not find file"))?;
  154. let mod_file_content = std::fs::read_to_string(&mod_file_path)
  155. .map_err(|_| ParseError::new_spanned(&item, "could not read file"))?;
  156. let mod_file = syn::parse_file(&mod_file_content)?;
  157. Self::new(
  158. parent_path.to_owned(),
  159. mod_file_path,
  160. item.ident.to_string(),
  161. mod_file.items,
  162. )
  163. }
  164. })
  165. }
  166. fn new(path: String, file: PathBuf, name: String, items: Vec<syn::Item>) -> Self {
  167. Self {
  168. name,
  169. file,
  170. path,
  171. items,
  172. }
  173. }
  174. fn unparsed_submodules(&self) -> Vec<UnparsedModule> {
  175. self.submodules()
  176. .map(|item| UnparsedModule {
  177. file: self.file.clone(),
  178. path: self.path.clone(),
  179. name: item.ident.to_string(),
  180. item: item.clone(),
  181. })
  182. .collect()
  183. }
  184. fn submodules(&self) -> impl Iterator<Item = &syn::ItemMod> {
  185. self.items.iter().filter_map(|i| match i {
  186. syn::Item::Mod(item) => Some(item),
  187. _ => None,
  188. })
  189. }
  190. fn structs(&self) -> impl Iterator<Item = &syn::ItemStruct> {
  191. self.items.iter().filter_map(|i| match i {
  192. syn::Item::Struct(item) => Some(item),
  193. _ => None,
  194. })
  195. }
  196. fn unsafe_struct_fields(&self) -> impl Iterator<Item = &syn::Field> {
  197. let accounts_filter = |item_struct: &&syn::ItemStruct| {
  198. item_struct.attrs.iter().any(|attr| {
  199. match attr.parse_meta() {
  200. Ok(syn::Meta::List(syn::MetaList{path, nested, ..})) => {
  201. path.is_ident("derive") && nested.iter().any(|nested| {
  202. matches!(nested, syn::NestedMeta::Meta(syn::Meta::Path(path)) if path.is_ident("Accounts"))
  203. })
  204. }
  205. _ => false
  206. }
  207. })
  208. };
  209. self.structs()
  210. .filter(accounts_filter)
  211. .flat_map(|s| &s.fields)
  212. .filter(|f| match &f.ty {
  213. syn::Type::Path(syn::TypePath {
  214. path: syn::Path { segments, .. },
  215. ..
  216. }) => {
  217. segments.len() == 1 && segments[0].ident == "UncheckedAccount"
  218. || segments[0].ident == "AccountInfo"
  219. }
  220. _ => false,
  221. })
  222. }
  223. fn enums(&self) -> impl Iterator<Item = &syn::ItemEnum> {
  224. self.items.iter().filter_map(|i| match i {
  225. syn::Item::Enum(item) => Some(item),
  226. _ => None,
  227. })
  228. }
  229. fn type_aliases(&self) -> impl Iterator<Item = &syn::ItemType> {
  230. self.items.iter().filter_map(|i| match i {
  231. syn::Item::Type(item) => Some(item),
  232. _ => None,
  233. })
  234. }
  235. fn consts(&self) -> impl Iterator<Item = &syn::ItemConst> {
  236. self.items.iter().filter_map(|i| match i {
  237. syn::Item::Const(item) => Some(item),
  238. _ => None,
  239. })
  240. }
  241. fn impl_consts(&self) -> impl Iterator<Item = (&Ident, &ImplItemConst)> {
  242. self.items
  243. .iter()
  244. .filter_map(|i| match i {
  245. syn::Item::Impl(syn::ItemImpl {
  246. self_ty: ty, items, ..
  247. }) => {
  248. if let Type::Path(TypePath {
  249. qself: None,
  250. path: p,
  251. }) = ty.as_ref()
  252. {
  253. if let Some(ident) = p.get_ident() {
  254. let mut to_return = Vec::new();
  255. items.iter().for_each(|item| {
  256. if let ImplItem::Const(item) = item {
  257. to_return.push((ident, item));
  258. }
  259. });
  260. Some(to_return)
  261. } else {
  262. None
  263. }
  264. } else {
  265. None
  266. }
  267. }
  268. _ => None,
  269. })
  270. .flatten()
  271. }
  272. }