├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── README.md ├── main.js └── src ├── checks.rs ├── lib.rs ├── main.rs └── reports.rs /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | *.*~ 3 | .DS_Store 4 | .harbor 5 | notes.org 6 | deploy.sh 7 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | [root] 2 | name = "cargo-safety" 3 | version = "0.1.0" 4 | dependencies = [ 5 | "glob 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", 6 | "serde 0.8.21 (registry+https://github.com/rust-lang/crates.io-index)", 7 | "serde_derive 0.8.21 (registry+https://github.com/rust-lang/crates.io-index)", 8 | "serde_json 0.8.4 (registry+https://github.com/rust-lang/crates.io-index)", 9 | "syntex 0.52.0 (registry+https://github.com/rust-lang/crates.io-index)", 10 | "syntex_errors 0.52.0 (registry+https://github.com/rust-lang/crates.io-index)", 11 | "syntex_syntax 0.52.0 (registry+https://github.com/rust-lang/crates.io-index)", 12 | ] 13 | 14 | [[package]] 15 | name = "bitflags" 16 | version = "0.7.0" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | 19 | [[package]] 20 | name = "dtoa" 21 | version = "0.2.2" 22 | source = "registry+https://github.com/rust-lang/crates.io-index" 23 | 24 | [[package]] 25 | name = "glob" 26 | version = "0.2.11" 27 | source = "registry+https://github.com/rust-lang/crates.io-index" 28 | 29 | [[package]] 30 | name = "itoa" 31 | version = "0.1.1" 32 | source = "registry+https://github.com/rust-lang/crates.io-index" 33 | 34 | [[package]] 35 | name = "kernel32-sys" 36 | version = "0.2.2" 37 | source = "registry+https://github.com/rust-lang/crates.io-index" 38 | dependencies = [ 39 | "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", 40 | "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", 41 | ] 42 | 43 | [[package]] 44 | name = "libc" 45 | version = "0.2.18" 46 | source = "registry+https://github.com/rust-lang/crates.io-index" 47 | 48 | [[package]] 49 | name = "log" 50 | version = "0.3.6" 51 | source = "registry+https://github.com/rust-lang/crates.io-index" 52 | 53 | [[package]] 54 | name = "num-traits" 55 | version = "0.1.36" 56 | source = "registry+https://github.com/rust-lang/crates.io-index" 57 | 58 | [[package]] 59 | name = "quote" 60 | version = "0.3.10" 61 | source = "registry+https://github.com/rust-lang/crates.io-index" 62 | 63 | [[package]] 64 | name = "rustc-serialize" 65 | version = "0.3.22" 66 | source = "registry+https://github.com/rust-lang/crates.io-index" 67 | 68 | [[package]] 69 | name = "serde" 70 | version = "0.8.21" 71 | source = "registry+https://github.com/rust-lang/crates.io-index" 72 | 73 | [[package]] 74 | name = "serde_codegen" 75 | version = "0.8.21" 76 | source = "registry+https://github.com/rust-lang/crates.io-index" 77 | dependencies = [ 78 | "quote 0.3.10 (registry+https://github.com/rust-lang/crates.io-index)", 79 | "serde_codegen_internals 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)", 80 | "syn 0.10.5 (registry+https://github.com/rust-lang/crates.io-index)", 81 | ] 82 | 83 | [[package]] 84 | name = "serde_codegen_internals" 85 | version = "0.11.3" 86 | source = "registry+https://github.com/rust-lang/crates.io-index" 87 | dependencies = [ 88 | "syn 0.10.5 (registry+https://github.com/rust-lang/crates.io-index)", 89 | ] 90 | 91 | [[package]] 92 | name = "serde_derive" 93 | version = "0.8.21" 94 | source = "registry+https://github.com/rust-lang/crates.io-index" 95 | dependencies = [ 96 | "serde_codegen 0.8.21 (registry+https://github.com/rust-lang/crates.io-index)", 97 | ] 98 | 99 | [[package]] 100 | name = "serde_json" 101 | version = "0.8.4" 102 | source = "registry+https://github.com/rust-lang/crates.io-index" 103 | dependencies = [ 104 | "dtoa 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", 105 | "itoa 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", 106 | "num-traits 0.1.36 (registry+https://github.com/rust-lang/crates.io-index)", 107 | "serde 0.8.21 (registry+https://github.com/rust-lang/crates.io-index)", 108 | ] 109 | 110 | [[package]] 111 | name = "syn" 112 | version = "0.10.5" 113 | source = "registry+https://github.com/rust-lang/crates.io-index" 114 | dependencies = [ 115 | "quote 0.3.10 (registry+https://github.com/rust-lang/crates.io-index)", 116 | "unicode-xid 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)", 117 | ] 118 | 119 | [[package]] 120 | name = "syntex" 121 | version = "0.52.0" 122 | source = "registry+https://github.com/rust-lang/crates.io-index" 123 | dependencies = [ 124 | "syntex_errors 0.52.0 (registry+https://github.com/rust-lang/crates.io-index)", 125 | "syntex_syntax 0.52.0 (registry+https://github.com/rust-lang/crates.io-index)", 126 | ] 127 | 128 | [[package]] 129 | name = "syntex_errors" 130 | version = "0.52.0" 131 | source = "registry+https://github.com/rust-lang/crates.io-index" 132 | dependencies = [ 133 | "libc 0.2.18 (registry+https://github.com/rust-lang/crates.io-index)", 134 | "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", 135 | "rustc-serialize 0.3.22 (registry+https://github.com/rust-lang/crates.io-index)", 136 | "syntex_pos 0.52.0 (registry+https://github.com/rust-lang/crates.io-index)", 137 | "term 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)", 138 | "unicode-xid 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)", 139 | ] 140 | 141 | [[package]] 142 | name = "syntex_pos" 143 | version = "0.52.0" 144 | source = "registry+https://github.com/rust-lang/crates.io-index" 145 | dependencies = [ 146 | "rustc-serialize 0.3.22 (registry+https://github.com/rust-lang/crates.io-index)", 147 | ] 148 | 149 | [[package]] 150 | name = "syntex_syntax" 151 | version = "0.52.0" 152 | source = "registry+https://github.com/rust-lang/crates.io-index" 153 | dependencies = [ 154 | "bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", 155 | "libc 0.2.18 (registry+https://github.com/rust-lang/crates.io-index)", 156 | "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", 157 | "rustc-serialize 0.3.22 (registry+https://github.com/rust-lang/crates.io-index)", 158 | "syntex_errors 0.52.0 (registry+https://github.com/rust-lang/crates.io-index)", 159 | "syntex_pos 0.52.0 (registry+https://github.com/rust-lang/crates.io-index)", 160 | "term 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)", 161 | "unicode-xid 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)", 162 | ] 163 | 164 | [[package]] 165 | name = "term" 166 | version = "0.4.4" 167 | source = "registry+https://github.com/rust-lang/crates.io-index" 168 | dependencies = [ 169 | "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", 170 | "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", 171 | ] 172 | 173 | [[package]] 174 | name = "unicode-xid" 175 | version = "0.0.3" 176 | source = "registry+https://github.com/rust-lang/crates.io-index" 177 | 178 | [[package]] 179 | name = "winapi" 180 | version = "0.2.8" 181 | source = "registry+https://github.com/rust-lang/crates.io-index" 182 | 183 | [[package]] 184 | name = "winapi-build" 185 | version = "0.1.1" 186 | source = "registry+https://github.com/rust-lang/crates.io-index" 187 | 188 | [metadata] 189 | "checksum bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "aad18937a628ec6abcd26d1489012cc0e18c21798210f491af69ded9b881106d" 190 | "checksum dtoa 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "0dd841b58510c9618291ffa448da2e4e0f699d984d436122372f446dae62263d" 191 | "checksum glob 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "8be18de09a56b60ed0edf84bc9df007e30040691af7acd1c41874faac5895bfb" 192 | "checksum itoa 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ae3088ea4baeceb0284ee9eea42f591226e6beaecf65373e41b38d95a1b8e7a1" 193 | "checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" 194 | "checksum libc 0.2.18 (registry+https://github.com/rust-lang/crates.io-index)" = "a51822fc847e7a8101514d1d44e354ba2ffa7d4c194dcab48870740e327cac70" 195 | "checksum log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "ab83497bf8bf4ed2a74259c1c802351fcd67a65baa86394b6ba73c36f4838054" 196 | "checksum num-traits 0.1.36 (registry+https://github.com/rust-lang/crates.io-index)" = "a16a42856a256b39c6d3484f097f6713e14feacd9bfb02290917904fae46c81c" 197 | "checksum quote 0.3.10 (registry+https://github.com/rust-lang/crates.io-index)" = "6732e32663c9c271bfc7c1823486b471f18c47a2dbf87c066897b7b51afc83be" 198 | "checksum rustc-serialize 0.3.22 (registry+https://github.com/rust-lang/crates.io-index)" = "237546c689f20bb44980270c73c3b9edd0891c1be49cc1274406134a66d3957b" 199 | "checksum serde 0.8.21 (registry+https://github.com/rust-lang/crates.io-index)" = "7b7c6bf11cf766473ea1d53eb4e3bc4e80f31f50082fc24077cf06f600279a66" 200 | "checksum serde_codegen 0.8.21 (registry+https://github.com/rust-lang/crates.io-index)" = "64e0d87d19ec28bf431ffa9bad1f1e4ea3b381cd616c6cc56dca9eedbc7f6ab8" 201 | "checksum serde_codegen_internals 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)" = "afad7924a009f859f380e4a2e3a509a845c2ac66435fcead74a4d983b21ae806" 202 | "checksum serde_derive 0.8.21 (registry+https://github.com/rust-lang/crates.io-index)" = "6e7ad1e74679b92730ca39c361ea125e2846df337c5d94d084eb2f7837c1843d" 203 | "checksum serde_json 0.8.4 (registry+https://github.com/rust-lang/crates.io-index)" = "3f7d3c184d35801fb8b32b46a7d58d57dbcc150b0eb2b46a1eb79645e8ecfd5b" 204 | "checksum syn 0.10.5 (registry+https://github.com/rust-lang/crates.io-index)" = "1a437f8b4353179418870f014113876cd4cd4f642e42dbc5ed4f328d5f808246" 205 | "checksum syntex 0.52.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3b1b66e8c1e25a6c4007a38a225411b776a06e8c3030d6fc4d200a038bf02065" 206 | "checksum syntex_errors 0.52.0 (registry+https://github.com/rust-lang/crates.io-index)" = "9e52bffe6202cfb67587784cf23e0ec5bf26d331eef4922a16d5c42e12aa1e9b" 207 | "checksum syntex_pos 0.52.0 (registry+https://github.com/rust-lang/crates.io-index)" = "955ef4b16af4c468e4680d1497f873ff288f557d338180649e18f915af5e15ac" 208 | "checksum syntex_syntax 0.52.0 (registry+https://github.com/rust-lang/crates.io-index)" = "76a302e717e348aa372ff577791c3832395650073b8d8432f8b3cb170b34afde" 209 | "checksum term 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "3deff8a2b3b6607d6d7cc32ac25c0b33709453ca9cceac006caac51e963cf94a" 210 | "checksum unicode-xid 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "36dff09cafb4ec7c8cf0023eb0b686cb6ce65499116a12201c9e11840ca01beb" 211 | "checksum winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" 212 | "checksum winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" 213 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "cargo-safety" 3 | version = "0.1.1" 4 | authors = ["Alex Kehayias"] 5 | description = "Safety checks for Rust crates" 6 | repository = "https://github.com/alexkehayias/cargo-safety" 7 | license = "EPL-1.0" 8 | keywords = ["cargo", "plugin", "safety", "safe", "checks"] 9 | 10 | [dependencies] 11 | syntex = "0.52.0" 12 | syntex_syntax = "0.52.0" 13 | syntex_errors = "0.52.0" 14 | serde = "0.8" 15 | serde_derive = "0.8" 16 | serde_json = "0.8" 17 | glob = "0.2" 18 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # `cargo safety` plugin 2 | 3 | This crate provides a subcommand for `cargo` that performs safety checks for Rust projects by finding all uses of `unsafe` code in dependencies. To do this reliably, the dependency tree is parsed by `cargo`, code is parsed into it's AST and then walked. Using Rust's wonderful pattern matching abilities we can quickly visit all places code can be `unsafe`. 4 | 5 | ## Why 6 | 7 | In coming to Rust, many people are drawn to it's promises of safety. I was curious to see how and why people were circumventing Rust's safety guarantees through the `unsafe` escape hatch. While I don't think we should reject any libraries that use unsafety, it is good to know where exactly things are unsafe so you can make an informed decision about the many libraries you may use. 8 | 9 | ## Try it 10 | 11 | Note: nightly build required 12 | 13 | ``` 14 | cargo install cargo-safety && cargo safety 15 | ``` 16 | 17 | Output (json): 18 | 19 | ``` 20 | [ 21 | { 22 | "lib_name": "gcc", 23 | "status": "failed", 24 | "offenses": [ 25 | { 26 | "occurences": "\/Users\/alexkehayias\/.cargo\/registry\/src\/github.com-1ecc6299db9ec823\/gcc-0.3.40\/src\/registry.rs:73:1: 73:29\n`unsafe impl Sync for Repr {}`\n", 27 | "kind": "unsafe_impl" 28 | } 29 | ] 30 | } 31 | ] 32 | ``` 33 | 34 | ## License 35 | 36 | Copyright © 2018 Alex Kehayias 37 | 38 | Distributed under the Eclipse Public License either version 1.0 or (at your option) any later version. 39 | -------------------------------------------------------------------------------- /main.js: -------------------------------------------------------------------------------- 1 | var child_process = require('child_process'); 2 | 3 | exports.handler = function(event, context, callback) { 4 | console.log(event); 5 | 6 | var git_url = event['body-json']['git-url']; 7 | var git_commit = event['body-json']['git-commit']; 8 | 9 | // Git commit is optional 10 | if (!!git_commit) { 11 | var args = [git_url, git_commit]; 12 | } else { 13 | var args = [git_url]; 14 | }; 15 | 16 | // spawn a child process to run the binary 17 | var proc = child_process.spawn( 18 | './target/release/harbor', 19 | args, 20 | // proc.stdin, pro.stdout, proc.stderr 21 | {stdio: ['ignore', 'pipe', 'ignore']} 22 | ); 23 | 24 | var out = ''; 25 | proc.stdout.on('data', function(data) { 26 | out += data.toString(); 27 | }); 28 | 29 | proc.on('close', function(code) { 30 | if(code !== 0) { 31 | err = new Error("Process exited with non-zero status code"); 32 | return callback(err, null); 33 | } 34 | 35 | // lol parsing from json just to have aws serialize it again :( 36 | return callback(null, JSON.parse(out)); 37 | }); 38 | } 39 | -------------------------------------------------------------------------------- /src/checks.rs: -------------------------------------------------------------------------------- 1 | use std::collections::{HashSet}; 2 | use syntex_syntax::codemap::{CodeMap, Span}; 3 | use syntex_syntax::ast::{NodeId, Block, FnDecl, Mac, Unsafety, BlockCheckMode, 4 | TraitItem, ImplItemKind, ImplItem, TraitItemKind, 5 | Attribute, MetaItemKind, Item, ItemKind}; 6 | use syntex_syntax::visit::{self, Visitor, FnKind}; 7 | 8 | 9 | #[allow(non_camel_case_types)] 10 | #[derive(Hash, Eq, PartialEq, Debug, Clone, Serialize)] 11 | enum UnsafeKind { 12 | unsafe_function, 13 | unsafe_impl, 14 | unsafe_impl_item, 15 | unsafe_block, 16 | unsafe_trait, 17 | unsafe_trait_item, 18 | unsafe_attr, 19 | } 20 | 21 | #[derive(Hash, Eq, PartialEq, Debug, Clone, Serialize)] 22 | pub struct UnsafeCode { 23 | kind: UnsafeKind, 24 | occurences: String, 25 | } 26 | 27 | impl UnsafeCode { 28 | fn new(kind: UnsafeKind, occurences: String) -> UnsafeCode { 29 | UnsafeCode {kind: kind, occurences: occurences} 30 | } 31 | } 32 | 33 | // The codemap is necessary to go from a `Span` to actual line & column 34 | // numbers for closures. 35 | pub struct UnsafeCrate<'a> { 36 | // Format { 37 | // kind: , 38 | // location: 39 | // } 40 | // 41 | pub locations: HashSet, 42 | // Used to go from a Span to line:column information 43 | pub codemap: &'a CodeMap, 44 | } 45 | 46 | // Unsafe code can be introduced in functions, blocks, traits, and implementations 47 | impl<'a> Visitor for UnsafeCrate<'a> { 48 | // Implement this otherwise it will panic when it hits a macro 49 | fn visit_mac(&mut self, _mac: &Mac) {} 50 | 51 | // Capture unsafe items i.e unsafe impl Trait for Foo 52 | fn visit_item(&mut self, item: &Item) { 53 | match item.node { 54 | ItemKind::Impl(unsafety, ..) => { 55 | match unsafety { 56 | Unsafety::Normal => (), 57 | Unsafety::Unsafe => { 58 | let record = UnsafeCode::new( 59 | UnsafeKind::unsafe_impl, 60 | self.codemap.span_to_expanded_string(item.span), 61 | ); 62 | self.locations.insert(record); 63 | }, 64 | } 65 | }, 66 | ItemKind::Trait(unsafety, ..) => { 67 | match unsafety { 68 | Unsafety::Normal => (), 69 | Unsafety::Unsafe => { 70 | let record = UnsafeCode::new( 71 | UnsafeKind::unsafe_trait, 72 | self.codemap.span_to_expanded_string(item.span), 73 | ); 74 | self.locations.insert(record); 75 | }, 76 | } 77 | }, 78 | _ => (), 79 | }; 80 | visit::walk_item(self, item); 81 | } 82 | 83 | // Recursively capture all occurences of unsafe functions 84 | fn visit_fn(&mut self, 85 | fn_kind: FnKind, 86 | fn_decl: &FnDecl, 87 | span: Span, 88 | _id: NodeId) { 89 | match fn_kind { 90 | FnKind::ItemFn(_, _, unsafety, _, _, _, _) => { 91 | match unsafety { 92 | Unsafety::Normal => (), 93 | Unsafety::Unsafe => { 94 | let record = UnsafeCode::new( 95 | UnsafeKind::unsafe_function, 96 | self.codemap.span_to_expanded_string(span), 97 | ); 98 | self.locations.insert(record); 99 | }, 100 | }; 101 | }, 102 | _ => (), 103 | }; 104 | visit::walk_fn(self, fn_kind, fn_decl, span); 105 | } 106 | 107 | // Recursively capture all unsafe blocks 108 | fn visit_block(&mut self, block: &Block) { 109 | match block.rules { 110 | BlockCheckMode::Default => (), 111 | BlockCheckMode::Unsafe(_) => { 112 | let record = UnsafeCode::new( 113 | UnsafeKind::unsafe_block, 114 | self.codemap.span_to_expanded_string(block.span), 115 | ); 116 | self.locations.insert(record); 117 | }, 118 | }; 119 | visit::walk_block(self, block); 120 | } 121 | 122 | // Capture any unsafe traits 123 | fn visit_trait_item(&mut self, ti: &TraitItem) { 124 | match ti.node { 125 | TraitItemKind::Method(ref sig, _) => match sig.unsafety { 126 | Unsafety::Normal => (), 127 | Unsafety::Unsafe => { 128 | let record = UnsafeCode::new( 129 | UnsafeKind::unsafe_trait_item, 130 | self.codemap.span_to_expanded_string(ti.span), 131 | ); 132 | self.locations.insert(record); 133 | }, 134 | }, 135 | _ => (), 136 | }; 137 | } 138 | 139 | // Capture any unsafe implementations 140 | fn visit_impl_item(&mut self, ii: &ImplItem) { 141 | match ii.node { 142 | ImplItemKind::Method(ref sig, _) => match sig.unsafety { 143 | Unsafety::Normal => (), 144 | Unsafety::Unsafe => { 145 | let record = UnsafeCode::new( 146 | UnsafeKind::unsafe_impl_item, 147 | self.codemap.span_to_expanded_string(ii.span), 148 | ); 149 | self.locations.insert(record); 150 | } 151 | }, 152 | _ => (), 153 | }; 154 | } 155 | 156 | // Capture unsafe destructor attribute i.e #["unsafe_destructor_blind_to_params"] 157 | fn visit_attribute(&mut self, attr: &Attribute) { 158 | match attr.value.node { 159 | MetaItemKind::Word => 160 | if attr.value.name == "unsafe_destructor_blind_to_params" { 161 | let record = UnsafeCode::new( 162 | UnsafeKind::unsafe_attr, 163 | self.codemap.span_to_expanded_string(attr.span), 164 | ); 165 | self.locations.insert(record); 166 | }, 167 | _ => (), 168 | }; 169 | } 170 | } 171 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | extern crate syntex_syntax; 2 | extern crate syntex_errors; 3 | #[macro_use] 4 | extern crate serde_derive; 5 | extern crate serde_json; 6 | 7 | pub mod checks; 8 | pub mod reports; 9 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | extern crate glob; 2 | extern crate syntex_syntax; 3 | extern crate syntex_errors; 4 | #[macro_use] 5 | extern crate serde_derive; 6 | extern crate serde_json; 7 | extern crate cargo_safety; 8 | 9 | use std::rc::Rc; 10 | use std::path::{Path}; 11 | use std::collections::{HashSet, HashMap}; 12 | use std::process::Command; 13 | use std::result::Result; 14 | use glob::glob; 15 | 16 | use syntex_syntax::codemap::{CodeMap}; 17 | use syntex_syntax::parse::{self, ParseSess}; 18 | use syntex_syntax::ast::{NodeId}; 19 | use syntex_syntax::visit::{Visitor}; 20 | use syntex_errors::{Handler}; 21 | use syntex_errors::emitter::{ColorConfig}; 22 | 23 | use cargo_safety::checks::{UnsafeCrate, UnsafeCode}; 24 | use cargo_safety::reports::{SafetyReport, Status}; 25 | 26 | 27 | // Returns false for any directories that should be excluded based on 28 | // cargo conventions 29 | pub fn is_valid_dir(file_path: &str) -> bool { 30 | !(file_path.contains("examples") || 31 | file_path.contains("target") || 32 | file_path.contains("tests") || 33 | file_path.contains("benches")) 34 | } 35 | 36 | #[test] 37 | fn test_is_valid_dir() { 38 | assert!(is_valid_dir(&"src/main.rs")); 39 | assert!(!is_valid_dir(&"benches/main.rs")); 40 | assert!(!is_valid_dir(&"examples/main.rs")); 41 | assert!(!is_valid_dir(&"tests/test.rs")); 42 | assert!(!is_valid_dir(&"target/test.rs")); 43 | } 44 | 45 | // Iterate through all files in the repo and return all safety infractions 46 | pub fn safety_infractions<'a>(root: &Path) -> HashSet { 47 | let codemap = Rc::new(CodeMap::new()); 48 | let tty_handler = Handler::with_tty_emitter(ColorConfig::Auto, true, false, Some(codemap.clone())); 49 | let parse_session = ParseSess::with_span_handler(tty_handler, codemap.clone()); 50 | 51 | glob(root.join("*.rs").to_str().unwrap()).expect("Failed to glob") 52 | .filter_map(Result::ok) 53 | .filter(|x| is_valid_dir(x.to_str().expect("Failed to coerce to string"))) 54 | .fold(HashSet::::new(), |accum, path_buf| { 55 | let file_path = path_buf.as_path(); 56 | 57 | let krate = parse::parse_crate_from_file(file_path, &parse_session).unwrap(); 58 | let mut unsafe_code = UnsafeCrate { 59 | locations: HashSet::::new(), 60 | codemap: &codemap, 61 | }; 62 | 63 | // Warning this has side-effects! 64 | unsafe_code.visit_mod(&krate.module, krate.span, NodeId::new(0)); 65 | 66 | if unsafe_code.locations.len() > 0 { 67 | accum.union(&unsafe_code.locations) 68 | .cloned() 69 | .collect::>() 70 | } else { 71 | accum 72 | } 73 | }) 74 | } 75 | 76 | #[derive(Debug, Deserialize, Serialize)] 77 | struct Dependency { 78 | features: Vec, 79 | kind: String, 80 | name: String, 81 | optional: bool, 82 | req: String, 83 | source: String, 84 | target: Option, 85 | uses_default_features: bool, 86 | } 87 | 88 | #[derive(Debug, Deserialize, Serialize)] 89 | struct Target { 90 | kind: Option>, 91 | name: String, 92 | src_path: String, 93 | } 94 | 95 | #[derive(Debug, Deserialize, Serialize)] 96 | struct Node { 97 | id: String, 98 | dependencies: Vec, 99 | } 100 | 101 | #[derive(Debug, Deserialize, Serialize)] 102 | struct ResolveMap { 103 | nodes: Vec, 104 | root: String, 105 | } 106 | 107 | #[derive(Debug, Deserialize, Serialize)] 108 | struct Package { 109 | id: String, 110 | name: String, 111 | version: String, 112 | manifest_path: String, 113 | features: Option>>, 114 | targets: Vec, 115 | dependencies: Vec, 116 | source: String, 117 | license_file: Option, 118 | license: String, 119 | } 120 | 121 | #[derive(Debug, Deserialize, Serialize)] 122 | struct Metadata { 123 | version: u32, 124 | resolve: ResolveMap, 125 | workspace_members: Vec, 126 | packages: Vec, 127 | } 128 | 129 | pub const USAGE: &'static str = " 130 | Compile a local package and all of its dependencies 131 | Usage: 132 | cargo safety 133 | Options: 134 | -h, --help Print this message 135 | "; 136 | 137 | pub fn main() { 138 | let output = Command::new("cargo").arg("metadata").output(); 139 | let stdout = output.unwrap().stdout; 140 | let json = String::from_utf8(stdout).expect("Failed reading cargo output"); 141 | let data: Metadata = serde_json::from_str(&json).expect("Failed to parse json"); 142 | 143 | let mut result: Vec = vec![]; 144 | for package in data.packages { 145 | for target in package.targets { 146 | let path = Path::new(&target.src_path).parent().unwrap(); 147 | let infractions = safety_infractions(path); 148 | let status = Status::from_bool(infractions.len() == 0); 149 | let report = SafetyReport::new(target.name, status, infractions); 150 | result.push(report); 151 | }; 152 | }; 153 | println!("{}", serde_json::to_string(&result).unwrap()); 154 | } 155 | -------------------------------------------------------------------------------- /src/reports.rs: -------------------------------------------------------------------------------- 1 | use std::collections::{HashSet}; 2 | use checks::{UnsafeCode}; 3 | 4 | 5 | #[allow(non_camel_case_types)] 6 | #[derive(Debug, Serialize)] 7 | pub enum Status { 8 | passed, 9 | failed, 10 | } 11 | 12 | impl Status { 13 | pub fn from_bool(pass: bool) -> Status { 14 | if pass { 15 | Status::passed 16 | } else { 17 | Status::failed 18 | } 19 | } 20 | } 21 | 22 | #[derive(Debug, Serialize)] 23 | pub struct SafetyReport { 24 | lib_name: String, 25 | status: Status, 26 | offenses: HashSet, 27 | } 28 | 29 | impl SafetyReport { 30 | pub fn new(lib_name: String, 31 | status: Status, 32 | offenses: HashSet) -> SafetyReport { 33 | SafetyReport {lib_name: lib_name, status: status, offenses: offenses} 34 | } 35 | } 36 | --------------------------------------------------------------------------------