|
| 1 | +use anyhow::anyhow; |
1 | 2 | use std::collections::BTreeMap; |
2 | 3 | use std::path::{Path, PathBuf}; |
3 | | - |
4 | 4 | use syn::parse::{Error as ParseError, Result as ParseResult}; |
5 | 5 |
|
6 | 6 | /// Crate parse context |
@@ -40,6 +40,41 @@ impl CrateContext { |
40 | 40 | modules: ParsedModule::parse_recursive(root.as_ref())?, |
41 | 41 | }) |
42 | 42 | } |
| 43 | + |
| 44 | + // Perform Anchor safety checks on the parsed create |
| 45 | + pub fn safety_checks(&self) -> Result<(), anyhow::Error> { |
| 46 | + // Check all structs for unsafe field types, i.e. AccountInfo and UncheckedAccount. |
| 47 | + for (_, ctx) in self.modules.iter() { |
| 48 | + for unsafe_field in ctx.unsafe_struct_fields() { |
| 49 | + // Check if unsafe field type has been documented with a /// SAFETY: doc string. |
| 50 | + let is_documented = unsafe_field.attrs.iter().any(|attr| { |
| 51 | + attr.tokens.clone().into_iter().any(|token| match token { |
| 52 | + // Check for doc comments containing CHECK |
| 53 | + proc_macro2::TokenTree::Literal(s) => s.to_string().contains("CHECK"), |
| 54 | + _ => false, |
| 55 | + }) |
| 56 | + }); |
| 57 | + if !is_documented { |
| 58 | + let ident = unsafe_field.ident.as_ref().unwrap(); |
| 59 | + let span = ident.span(); |
| 60 | + // Error if undocumented. |
| 61 | + return Err(anyhow!( |
| 62 | + r#" |
| 63 | + {}:{}:{} |
| 64 | + Struct field "{}" is unsafe, but is not documented. |
| 65 | + Please add a `/// CHECK:` doc comment explaining why no checks through types are necessary. |
| 66 | + See https://book.anchor-lang.com/chapter_3/the_accounts_struct.html#safety-checks for more information. |
| 67 | + "#, |
| 68 | + ctx.file.canonicalize().unwrap().display(), |
| 69 | + span.start().line, |
| 70 | + span.start().column, |
| 71 | + ident.to_string() |
| 72 | + )); |
| 73 | + }; |
| 74 | + } |
| 75 | + } |
| 76 | + Ok(()) |
| 77 | + } |
43 | 78 | } |
44 | 79 |
|
45 | 80 | /// Module parse context |
@@ -181,6 +216,21 @@ impl ParsedModule { |
181 | 216 | }) |
182 | 217 | } |
183 | 218 |
|
| 219 | + fn unsafe_struct_fields(&self) -> impl Iterator<Item = &syn::Field> { |
| 220 | + self.structs() |
| 221 | + .flat_map(|s| &s.fields) |
| 222 | + .filter(|f| match &f.ty { |
| 223 | + syn::Type::Path(syn::TypePath { |
| 224 | + path: syn::Path { segments, .. }, |
| 225 | + .. |
| 226 | + }) => { |
| 227 | + segments.len() == 1 && segments[0].ident == "UncheckedAccount" |
| 228 | + || segments[0].ident == "AccountInfo" |
| 229 | + } |
| 230 | + _ => false, |
| 231 | + }) |
| 232 | + } |
| 233 | + |
184 | 234 | fn enums(&self) -> impl Iterator<Item = &syn::ItemEnum> { |
185 | 235 | self.items.iter().filter_map(|i| match i { |
186 | 236 | syn::Item::Enum(item) => Some(item), |
|
0 commit comments