├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── Readme.org ├── example_code ├── Cargo.toml └── src │ ├── fun_method.rs │ └── main.rs ├── rust-toolchain.toml └── src ├── bin ├── callgraph.rs └── cargo-callgraph.rs ├── lib.rs └── visitor.rs /.gitignore: -------------------------------------------------------------------------------- 1 | debug/ 2 | target/ 3 | 4 | Cargo.lock 5 | rust-toolchain.toml 6 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "bitflags" 7 | version = "2.6.0" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" 10 | 11 | [[package]] 12 | name = "callgraph" 13 | version = "0.1.0" 14 | dependencies = [ 15 | "cargo_metadata", 16 | "rustc_version", 17 | "serde", 18 | "serde_json", 19 | "wait-timeout", 20 | "which", 21 | ] 22 | 23 | [[package]] 24 | name = "camino" 25 | version = "1.1.7" 26 | source = "registry+https://github.com/rust-lang/crates.io-index" 27 | checksum = "e0ec6b951b160caa93cc0c7b209e5a3bff7aae9062213451ac99493cd844c239" 28 | dependencies = [ 29 | "serde", 30 | ] 31 | 32 | [[package]] 33 | name = "cargo-platform" 34 | version = "0.1.8" 35 | source = "registry+https://github.com/rust-lang/crates.io-index" 36 | checksum = "24b1f0365a6c6bb4020cd05806fd0d33c44d38046b8bd7f0e40814b9763cabfc" 37 | dependencies = [ 38 | "serde", 39 | ] 40 | 41 | [[package]] 42 | name = "cargo_metadata" 43 | version = "0.18.1" 44 | source = "registry+https://github.com/rust-lang/crates.io-index" 45 | checksum = "2d886547e41f740c616ae73108f6eb70afe6d940c7bc697cb30f13daec073037" 46 | dependencies = [ 47 | "camino", 48 | "cargo-platform", 49 | "semver", 50 | "serde", 51 | "serde_json", 52 | "thiserror", 53 | ] 54 | 55 | [[package]] 56 | name = "either" 57 | version = "1.13.0" 58 | source = "registry+https://github.com/rust-lang/crates.io-index" 59 | checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" 60 | 61 | [[package]] 62 | name = "errno" 63 | version = "0.3.9" 64 | source = "registry+https://github.com/rust-lang/crates.io-index" 65 | checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" 66 | dependencies = [ 67 | "libc", 68 | "windows-sys", 69 | ] 70 | 71 | [[package]] 72 | name = "home" 73 | version = "0.5.9" 74 | source = "registry+https://github.com/rust-lang/crates.io-index" 75 | checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5" 76 | dependencies = [ 77 | "windows-sys", 78 | ] 79 | 80 | [[package]] 81 | name = "itoa" 82 | version = "1.0.11" 83 | source = "registry+https://github.com/rust-lang/crates.io-index" 84 | checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" 85 | 86 | [[package]] 87 | name = "libc" 88 | version = "0.2.155" 89 | source = "registry+https://github.com/rust-lang/crates.io-index" 90 | checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" 91 | 92 | [[package]] 93 | name = "linux-raw-sys" 94 | version = "0.4.14" 95 | source = "registry+https://github.com/rust-lang/crates.io-index" 96 | checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" 97 | 98 | [[package]] 99 | name = "memchr" 100 | version = "2.7.4" 101 | source = "registry+https://github.com/rust-lang/crates.io-index" 102 | checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" 103 | 104 | [[package]] 105 | name = "proc-macro2" 106 | version = "1.0.86" 107 | source = "registry+https://github.com/rust-lang/crates.io-index" 108 | checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" 109 | dependencies = [ 110 | "unicode-ident", 111 | ] 112 | 113 | [[package]] 114 | name = "quote" 115 | version = "1.0.36" 116 | source = "registry+https://github.com/rust-lang/crates.io-index" 117 | checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" 118 | dependencies = [ 119 | "proc-macro2", 120 | ] 121 | 122 | [[package]] 123 | name = "rustc_version" 124 | version = "0.4.0" 125 | source = "registry+https://github.com/rust-lang/crates.io-index" 126 | checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" 127 | dependencies = [ 128 | "semver", 129 | ] 130 | 131 | [[package]] 132 | name = "rustix" 133 | version = "0.38.34" 134 | source = "registry+https://github.com/rust-lang/crates.io-index" 135 | checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" 136 | dependencies = [ 137 | "bitflags", 138 | "errno", 139 | "libc", 140 | "linux-raw-sys", 141 | "windows-sys", 142 | ] 143 | 144 | [[package]] 145 | name = "ryu" 146 | version = "1.0.18" 147 | source = "registry+https://github.com/rust-lang/crates.io-index" 148 | checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" 149 | 150 | [[package]] 151 | name = "semver" 152 | version = "1.0.23" 153 | source = "registry+https://github.com/rust-lang/crates.io-index" 154 | checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" 155 | dependencies = [ 156 | "serde", 157 | ] 158 | 159 | [[package]] 160 | name = "serde" 161 | version = "1.0.204" 162 | source = "registry+https://github.com/rust-lang/crates.io-index" 163 | checksum = "bc76f558e0cbb2a839d37354c575f1dc3fdc6546b5be373ba43d95f231bf7c12" 164 | dependencies = [ 165 | "serde_derive", 166 | ] 167 | 168 | [[package]] 169 | name = "serde_derive" 170 | version = "1.0.204" 171 | source = "registry+https://github.com/rust-lang/crates.io-index" 172 | checksum = "e0cd7e117be63d3c3678776753929474f3b04a43a080c744d6b0ae2a8c28e222" 173 | dependencies = [ 174 | "proc-macro2", 175 | "quote", 176 | "syn", 177 | ] 178 | 179 | [[package]] 180 | name = "serde_json" 181 | version = "1.0.122" 182 | source = "registry+https://github.com/rust-lang/crates.io-index" 183 | checksum = "784b6203951c57ff748476b126ccb5e8e2959a5c19e5c617ab1956be3dbc68da" 184 | dependencies = [ 185 | "itoa", 186 | "memchr", 187 | "ryu", 188 | "serde", 189 | ] 190 | 191 | [[package]] 192 | name = "syn" 193 | version = "2.0.72" 194 | source = "registry+https://github.com/rust-lang/crates.io-index" 195 | checksum = "dc4b9b9bf2add8093d3f2c0204471e951b2285580335de42f9d2534f3ae7a8af" 196 | dependencies = [ 197 | "proc-macro2", 198 | "quote", 199 | "unicode-ident", 200 | ] 201 | 202 | [[package]] 203 | name = "thiserror" 204 | version = "1.0.63" 205 | source = "registry+https://github.com/rust-lang/crates.io-index" 206 | checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724" 207 | dependencies = [ 208 | "thiserror-impl", 209 | ] 210 | 211 | [[package]] 212 | name = "thiserror-impl" 213 | version = "1.0.63" 214 | source = "registry+https://github.com/rust-lang/crates.io-index" 215 | checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" 216 | dependencies = [ 217 | "proc-macro2", 218 | "quote", 219 | "syn", 220 | ] 221 | 222 | [[package]] 223 | name = "unicode-ident" 224 | version = "1.0.12" 225 | source = "registry+https://github.com/rust-lang/crates.io-index" 226 | checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" 227 | 228 | [[package]] 229 | name = "wait-timeout" 230 | version = "0.2.0" 231 | source = "registry+https://github.com/rust-lang/crates.io-index" 232 | checksum = "9f200f5b12eb75f8c1ed65abd4b2db8a6e1b138a20de009dacee265a2498f3f6" 233 | dependencies = [ 234 | "libc", 235 | ] 236 | 237 | [[package]] 238 | name = "which" 239 | version = "6.0.2" 240 | source = "registry+https://github.com/rust-lang/crates.io-index" 241 | checksum = "3d9c5ed668ee1f17edb3b627225343d210006a90bb1e3745ce1f30b1fb115075" 242 | dependencies = [ 243 | "either", 244 | "home", 245 | "rustix", 246 | "winsafe", 247 | ] 248 | 249 | [[package]] 250 | name = "windows-sys" 251 | version = "0.52.0" 252 | source = "registry+https://github.com/rust-lang/crates.io-index" 253 | checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" 254 | dependencies = [ 255 | "windows-targets", 256 | ] 257 | 258 | [[package]] 259 | name = "windows-targets" 260 | version = "0.52.6" 261 | source = "registry+https://github.com/rust-lang/crates.io-index" 262 | checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" 263 | dependencies = [ 264 | "windows_aarch64_gnullvm", 265 | "windows_aarch64_msvc", 266 | "windows_i686_gnu", 267 | "windows_i686_gnullvm", 268 | "windows_i686_msvc", 269 | "windows_x86_64_gnu", 270 | "windows_x86_64_gnullvm", 271 | "windows_x86_64_msvc", 272 | ] 273 | 274 | [[package]] 275 | name = "windows_aarch64_gnullvm" 276 | version = "0.52.6" 277 | source = "registry+https://github.com/rust-lang/crates.io-index" 278 | checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" 279 | 280 | [[package]] 281 | name = "windows_aarch64_msvc" 282 | version = "0.52.6" 283 | source = "registry+https://github.com/rust-lang/crates.io-index" 284 | checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" 285 | 286 | [[package]] 287 | name = "windows_i686_gnu" 288 | version = "0.52.6" 289 | source = "registry+https://github.com/rust-lang/crates.io-index" 290 | checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" 291 | 292 | [[package]] 293 | name = "windows_i686_gnullvm" 294 | version = "0.52.6" 295 | source = "registry+https://github.com/rust-lang/crates.io-index" 296 | checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" 297 | 298 | [[package]] 299 | name = "windows_i686_msvc" 300 | version = "0.52.6" 301 | source = "registry+https://github.com/rust-lang/crates.io-index" 302 | checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" 303 | 304 | [[package]] 305 | name = "windows_x86_64_gnu" 306 | version = "0.52.6" 307 | source = "registry+https://github.com/rust-lang/crates.io-index" 308 | checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" 309 | 310 | [[package]] 311 | name = "windows_x86_64_gnullvm" 312 | version = "0.52.6" 313 | source = "registry+https://github.com/rust-lang/crates.io-index" 314 | checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" 315 | 316 | [[package]] 317 | name = "windows_x86_64_msvc" 318 | version = "0.52.6" 319 | source = "registry+https://github.com/rust-lang/crates.io-index" 320 | checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" 321 | 322 | [[package]] 323 | name = "winsafe" 324 | version = "0.0.19" 325 | source = "registry+https://github.com/rust-lang/crates.io-index" 326 | checksum = "d135d17ab770252ad95e9a872d365cf3090e3be864a34ab46f48555993efc904" 327 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "callgraph" 3 | version = "0.1.0" 4 | authors = ["vikramnitin9 "] 5 | edition = "2021" 6 | 7 | [dependencies] 8 | cargo_metadata = "0.18.1" 9 | serde = { version = "1.0.204", features = ["derive"] } 10 | serde_json = "1.0.122" 11 | rustc_version = "0.4.0" 12 | wait-timeout = "0.2.0" 13 | which = "6.0.2" 14 | 15 | [[bin]] 16 | name = "callgraph" 17 | 18 | [[bin]] 19 | name = "cargo-callgraph" -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2019 heinzelotto 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | 9 | Note: this includes code by The Rust Project Developers. 10 | -------------------------------------------------------------------------------- /Readme.org: -------------------------------------------------------------------------------- 1 | * rust-callgraph 2 | 3 | Computes the callgraph of rust programs. 4 | 5 | Works with Rustc nightly 1.82.0 (`nightly-2024-08-07`). Works with full Cargo projects. 6 | 7 | * Installation 8 | #+BEGIN_SRC sh 9 | export RUSTFLAGS="-L $RUSTUP_HOME/toolchains/nightly-2024-08-07-x86_64-unknown-linux-gnu/lib" 10 | export LD_LIBRARY_PATH="$LD_LIBRARY_PATH:$RUSTUP_HOME/toolchains/nightly-2024-08-07-x86_64-unknown-linux-gnu/lib" 11 | cargo install --debug --locked --path . --force 12 | #+END_SRC 13 | 14 | * Usage 15 | 16 | #+BEGIN_SRC sh 17 | cd ./example_code 18 | cargo callgraph # To run on a full Cargo project 19 | callgraph src/main.rs # To run on a single file 20 | #+END_SRC 21 | 22 | * How it works 23 | 24 | Using the rustc interface, run the compiler until after analysis stage, walk the expanded syntax tree and remember function and method definitions and resolved calls. 25 | 26 | * Features 27 | Extracts all kinds of calls: 28 | - function defs 29 | - struct associated impl methods 30 | - trait method declarations 31 | - trait method default impl 32 | - trait method implementations 33 | - statically dispatched calls 34 | - dynamically dispatched calls 35 | 36 | Missing features: 37 | - requires a nightly toolchain (this will not change since the rustc interface will not be stabilized) 38 | - rudimentary debugging output, no real output 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /example_code/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "example_code" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | -------------------------------------------------------------------------------- /example_code/src/fun_method.rs: -------------------------------------------------------------------------------- 1 | pub trait T { 2 | fn bla(&self){let _g=3;} 3 | } 4 | 5 | pub struct S; 6 | 7 | impl S { 8 | pub fn met(&self) { 9 | let _k = 44; 10 | } 11 | } 12 | 13 | impl T for S { 14 | fn bla(&self) { 15 | let _i = 6; 16 | } 17 | } 18 | 19 | pub struct R; 20 | 21 | impl T for R { 22 | fn bla(&self) { 23 | let _x = 4; 24 | } 25 | } 26 | 27 | pub fn _virt(ob: &dyn T) { 28 | ob.bla(); 29 | } 30 | -------------------------------------------------------------------------------- /example_code/src/main.rs: -------------------------------------------------------------------------------- 1 | mod fun_method; 2 | 3 | use fun_method::{S, T}; 4 | 5 | fn main() { 6 | let s = S {}; 7 | s.met(); 8 | s.bla(); 9 | } 10 | -------------------------------------------------------------------------------- /rust-toolchain.toml: -------------------------------------------------------------------------------- 1 | [toolchain] 2 | channel = "nightly-2024-08-07" 3 | components = [ "rustc-dev" ] 4 | -------------------------------------------------------------------------------- /src/bin/callgraph.rs: -------------------------------------------------------------------------------- 1 | #![feature(rustc_private)] 2 | 3 | extern crate rustc_driver; 4 | extern crate rustc_interface; 5 | 6 | use rustc_driver::{Callbacks, Compilation}; 7 | use rustc_interface::Queries; 8 | use rustc_interface::interface::Compiler; 9 | 10 | use callgraph::{analyze, compile_time_sysroot}; 11 | 12 | struct CallgraphCallbacks; 13 | 14 | impl Callbacks for CallgraphCallbacks { 15 | 16 | fn after_analysis<'tcx>(&mut self, _compiler: &Compiler, queries: &'tcx Queries<'tcx>) -> Compilation { 17 | // let expanded_crate = &compiler.expansion().unwrap().peek().0; 18 | queries.global_ctxt().unwrap().enter(|tcx| { 19 | analyze(&tcx); 20 | }); 21 | 22 | Compilation::Stop 23 | } 24 | } 25 | 26 | fn main() { 27 | let mut args: Vec<_> = std::env::args().collect(); 28 | 29 | // Make sure we use the right default sysroot. The default sysroot is wrong, 30 | // because `get_or_default_sysroot` in `librustc_session` bases that on `current_exe`. 31 | // 32 | // Make sure we always call `compile_time_sysroot` as that also does some sanity-checks 33 | // of the environment we were built in. 34 | // FIXME: Ideally we'd turn a bad build env into a compile-time error via CTFE or so. 35 | if let Some(sysroot) = compile_time_sysroot() { 36 | let sysroot_flag = "--sysroot"; 37 | if !args.iter().any(|e| e == sysroot_flag) { 38 | // We need to overwrite the default that librustc_session would compute. 39 | args.push(sysroot_flag.to_owned()); 40 | args.push(sysroot); 41 | } 42 | } 43 | 44 | let mut calls = CallgraphCallbacks; 45 | 46 | let run_compiler = rustc_driver::RunCompiler::new(&args, &mut calls); 47 | run_compiler.run(); 48 | } 49 | -------------------------------------------------------------------------------- /src/bin/cargo-callgraph.rs: -------------------------------------------------------------------------------- 1 | 2 | ///! This implementation is based on `cargo-miri` 3 | ///! https://github.com/rust-lang/miri/blob/master/src/bin/cargo-miri.rs 4 | 5 | use std::env; 6 | use std::fmt::Display; 7 | use std::path::{Path, PathBuf}; 8 | use std::process::Command; 9 | use std::time::Duration; 10 | 11 | use rustc_version::VersionMeta; 12 | 13 | use wait_timeout::ChildExt; 14 | 15 | fn show_error(msg: impl AsRef) -> ! { 16 | println!("{}", msg.as_ref()); 17 | std::process::exit(1) 18 | } 19 | 20 | // Determines whether a `--flag` is present. 21 | fn has_arg_flag(name: &str) -> bool { 22 | // Stop searching at `--`. 23 | let mut args = std::env::args().take_while(|val| val != "--"); 24 | args.any(|val| val == name) 25 | } 26 | 27 | /// Gets the value of a `--flag`. 28 | fn get_arg_flag_value(name: &str) -> Option { 29 | // Stop searching at `--`. 30 | let mut args = std::env::args().take_while(|val| val != "--"); 31 | loop { 32 | let arg = match args.next() { 33 | Some(arg) => arg, 34 | None => return None, 35 | }; 36 | if !arg.starts_with(name) { 37 | continue; 38 | } 39 | // Strip leading `name`. 40 | let suffix = &arg[name.len()..]; 41 | if suffix.is_empty() { 42 | // This argument is exactly `name`; the next one is the value. 43 | return args.next(); 44 | } else if suffix.starts_with('=') { 45 | // This argument is `name=value`; get the value. 46 | // Strip leading `=`. 47 | return Some(suffix[1..].to_owned()); 48 | } 49 | } 50 | } 51 | 52 | fn any_arg_flag(name: &str, mut check: F) -> bool 53 | where 54 | F: FnMut(&str) -> bool, 55 | { 56 | // Stop searching at `--`. 57 | let mut args = std::env::args().take_while(|val| val != "--"); 58 | loop { 59 | let arg = match args.next() { 60 | Some(arg) => arg, 61 | None => return false, 62 | }; 63 | if !arg.starts_with(name) { 64 | continue; 65 | } 66 | 67 | // Strip leading `name`. 68 | let suffix = &arg[name.len()..]; 69 | let value = if suffix.is_empty() { 70 | // This argument is exactly `name`; the next one is the value. 71 | match args.next() { 72 | Some(arg) => arg, 73 | None => return false, 74 | } 75 | } else if suffix.starts_with('=') { 76 | // This argument is `name=value`; get the value. 77 | // Strip leading `=`. 78 | suffix[1..].to_owned() 79 | } else { 80 | return false; 81 | }; 82 | 83 | if check(&value) { 84 | return true; 85 | } 86 | } 87 | } 88 | 89 | /// Finds the first argument ends with `.rs`. 90 | fn get_first_arg_with_rs_suffix() -> Option { 91 | // Stop searching at `--`. 92 | let mut args = std::env::args().take_while(|val| val != "--"); 93 | args.find(|arg| arg.ends_with(".rs")) 94 | } 95 | 96 | fn version_info() -> VersionMeta { 97 | VersionMeta::for_command(Command::new(find_callgraph())) 98 | .expect("failed to determine underlying rustc version of Callgraph") 99 | } 100 | 101 | fn cargo_package() -> cargo_metadata::Package { 102 | // We need to get the manifest, and then the metadata, to enumerate targets. 103 | let manifest_path = 104 | get_arg_flag_value("--manifest-path").map(|m| Path::new(&m).canonicalize().unwrap()); 105 | 106 | let mut cmd = cargo_metadata::MetadataCommand::new(); 107 | if let Some(manifest_path) = &manifest_path { 108 | cmd.manifest_path(manifest_path); 109 | } 110 | let mut metadata = match cmd.exec() { 111 | Ok(metadata) => metadata, 112 | Err(e) => show_error(format!("Could not obtain Cargo metadata\n{}", e)), 113 | }; 114 | let current_dir = std::env::current_dir(); 115 | 116 | let package_index = metadata 117 | .packages 118 | .iter() 119 | .position(|package| { 120 | let package_manifest_path = Path::new(&package.manifest_path); 121 | 122 | if let Some(manifest_path) = &manifest_path { 123 | package_manifest_path == manifest_path 124 | } else { 125 | let current_dir = current_dir 126 | .as_ref() 127 | .expect("could not read current directory"); 128 | let package_manifest_directory = package_manifest_path 129 | .parent() 130 | .expect("could not find parent directory of package manifest"); 131 | package_manifest_directory == current_dir 132 | } 133 | }) 134 | .unwrap_or_else(|| { 135 | show_error("This seems to be a workspace, which is not supported by cargo-callgraph"); 136 | }); 137 | 138 | metadata.packages.remove(package_index) 139 | } 140 | 141 | /// Returns the path to the `callgraph` binary 142 | fn find_callgraph() -> PathBuf { 143 | let mut path = std::env::current_exe().expect("current executable path invalid"); 144 | path.set_file_name("callgraph"); 145 | path 146 | } 147 | 148 | /// Make sure that the `callgraph` and `rustc` binary are from the same sysroot. 149 | /// This can be violated e.g. when callgraph is locally built and installed with a different 150 | /// toolchain than what is used when `cargo callgraph` is run. 151 | fn test_sysroot_consistency() { 152 | fn get_sysroot(mut cmd: Command) -> PathBuf { 153 | let out = cmd 154 | .arg("--print") 155 | .arg("sysroot") 156 | .output() 157 | .expect("Failed to run rustc to get sysroot info"); 158 | let stdout = String::from_utf8(out.stdout).expect("stdout is not valid UTF-8"); 159 | let stderr = String::from_utf8(out.stderr).expect("stderr is not valid UTF-8"); 160 | let stdout = stdout.trim(); 161 | assert!( 162 | out.status.success(), 163 | "Bad status code when getting sysroot info.\nstdout:\n{}\nstderr:\n{}", 164 | stdout, 165 | stderr 166 | ); 167 | PathBuf::from(stdout) 168 | .canonicalize() 169 | .unwrap_or_else(|_| panic!("Failed to canonicalize sysroot: {}", stdout)) 170 | } 171 | 172 | let rustc_sysroot = get_sysroot(Command::new("rustc")); 173 | let callgraph_sysroot = get_sysroot(Command::new(find_callgraph())); 174 | 175 | if rustc_sysroot != callgraph_sysroot { 176 | show_error(format!( 177 | "callgraph was built for a different sysroot than the rustc in your current toolchain.\n\ 178 | Make sure you use the same toolchain to run callgraph that you used to build it!\n\ 179 | rustc sysroot: `{}`\n\ 180 | callgraph sysroot: `{}`", 181 | rustc_sysroot.display(), 182 | callgraph_sysroot.display() 183 | )); 184 | } 185 | } 186 | 187 | fn clean_package(package_name: &str) { 188 | let mut cmd = Command::new("cargo"); 189 | cmd.arg("clean"); 190 | 191 | cmd.arg("-p"); 192 | cmd.arg(package_name); 193 | 194 | cmd.arg("--target"); 195 | cmd.arg(version_info().host); 196 | 197 | let exit_status = cmd 198 | .spawn() 199 | .expect("could not run cargo clean") 200 | .wait() 201 | .expect("failed to wait for cargo?"); 202 | 203 | if !exit_status.success() { 204 | show_error(format!("cargo clean failed")); 205 | } 206 | } 207 | 208 | fn main() { 209 | 210 | let mut args = std::env::args(); 211 | // Skip binary name. 212 | args.next().unwrap(); 213 | 214 | let Some(first) = args.next() else { 215 | show_error( 216 | "`cargo-callgraph` called without first argument; please only invoke this binary through `cargo callgraph`" 217 | ) 218 | }; 219 | 220 | match first.as_str() { 221 | 222 | "callgraph" => { 223 | eprintln!("Running cargo callgraph"); 224 | // This arm is for when `cargo callgraph` is called. We call `cargo rustc` for each applicable target, 225 | // but with the `RUSTC` env var set to the `cargo-callgraph` binary so that we come back in the other branch, 226 | // and dispatch the invocations to `rustc` and `callgraph`, respectively. 227 | in_cargo_callgraph(); 228 | eprintln!("cargo callgraph finished"); 229 | }, 230 | // Check if arg is a path that ends with "/rustc" 231 | arg if arg.ends_with("rustc") => { 232 | eprintln!("Running cargo rustc"); 233 | // This arm is executed when `cargo-callgraph` runs `cargo rustc` with the `RUSTC_WRAPPER` env var set to itself: 234 | // dependencies get dispatched to `rustc`, the final test/binary to `callgraph`. 235 | inside_cargo_rustc(); 236 | eprintln!("cargo rustc finished"); 237 | }, 238 | _ => { 239 | show_error( 240 | "`cargo-callgraph` must be called with either `callgraph` or `rustc` as first argument.", 241 | ); 242 | } 243 | } 244 | } 245 | 246 | #[repr(u8)] 247 | enum TargetKind { 248 | Library = 0, 249 | Bin, 250 | Unknown, 251 | } 252 | 253 | impl TargetKind { 254 | fn is_lib_str(s: &str) -> bool { 255 | s == "lib" || s == "rlib" || s == "staticlib" 256 | } 257 | } 258 | 259 | impl From<&cargo_metadata::Target> for TargetKind { 260 | fn from(target: &cargo_metadata::Target) -> Self { 261 | if target.kind.iter().any(|s| TargetKind::is_lib_str(s)) { 262 | TargetKind::Library 263 | } else if let Some("bin") = target.kind.get(0).map(|s| s.as_ref()) { 264 | TargetKind::Bin 265 | } else { 266 | TargetKind::Unknown 267 | } 268 | } 269 | } 270 | 271 | impl Display for TargetKind { 272 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 273 | write!( 274 | f, 275 | "{}", 276 | match self { 277 | TargetKind::Library => "lib", 278 | TargetKind::Bin => "bin", 279 | TargetKind::Unknown => "unknown", 280 | } 281 | ) 282 | } 283 | } 284 | 285 | fn in_cargo_callgraph() { 286 | let verbose = has_arg_flag("-v"); 287 | 288 | // Some basic sanity checks 289 | test_sysroot_consistency(); 290 | 291 | // Now run the command. 292 | let package = cargo_package(); 293 | let mut targets: Vec<_> = package.targets.into_iter().collect(); 294 | 295 | // Ensure `lib` is compiled before `bin` 296 | targets.sort_by_key(|target| TargetKind::from(target) as u8); 297 | 298 | for target in targets { 299 | // Skip `cargo callgraph` 300 | let mut args = std::env::args().skip(2); 301 | let kind = TargetKind::from(&target); 302 | 303 | println!("Target name: {}", &target.name); 304 | 305 | // Now we run `cargo check $FLAGS $ARGS`, giving the user the 306 | // change to add additional arguments. `FLAGS` is set to identify 307 | // this target. The user gets to control what gets actually passed to Callgraph. 308 | let mut cmd = Command::new("cargo"); 309 | cmd.arg("check"); 310 | 311 | match kind { 312 | TargetKind::Bin => { 313 | // Analyze all the binaries. 314 | cmd.arg("--bin").arg(&target.name); 315 | } 316 | TargetKind::Library => { 317 | // There can be only one lib in a crate. 318 | cmd.arg("--lib"); 319 | // Clean the result to disable Cargo's freshness check 320 | clean_package(&package.name); 321 | } 322 | TargetKind::Unknown => { 323 | println!( 324 | "Target {}:{} is not supported", 325 | target.kind.as_slice().join("/"), 326 | &target.name 327 | ); 328 | continue; 329 | } 330 | } 331 | 332 | if !cfg!(debug_assertions) && !verbose { 333 | cmd.arg("-q"); 334 | } 335 | 336 | // Forward user-defined `cargo` args until first `--`. 337 | while let Some(arg) = args.next() { 338 | if arg == "--" { 339 | break; 340 | } 341 | cmd.arg(arg); 342 | } 343 | 344 | // We want to always run `cargo` with `--target`. This later helps us detect 345 | // which crates are proc-macro/build-script (host crates) and which crates are 346 | // needed for the program itself. 347 | if get_arg_flag_value("--target").is_none() { 348 | // When no `--target` is given, default to the host. 349 | cmd.arg("--target"); 350 | cmd.arg(version_info().host); 351 | } 352 | 353 | // Serialize the remaining args into a special environment variable. 354 | // This will be read by `inside_cargo_rustc` when we go to invoke 355 | // our actual target crate (the binary or the test we are running). 356 | // Since we're using "cargo check", we have no other way of passing 357 | // these arguments. 358 | let args_vec: Vec = args.collect(); 359 | cmd.env( 360 | "CALLGRAPH_ARGS", 361 | serde_json::to_string(&args_vec).expect("failed to serialize args"), 362 | ); 363 | 364 | // Set `RUSTC_WRAPPER` to ourselves. Cargo will prepend that binary to its usual invocation, 365 | // i.e., the first argument is `rustc` -- which is what we use in `main` to distinguish 366 | // the two codepaths. 367 | if env::var_os("RUSTC_WRAPPER").is_some() { 368 | eprintln!("WARNING: Ignoring existing `RUSTC_WRAPPER` environment variable, Callgraph does not support wrapping."); 369 | } 370 | 371 | let path = std::env::current_exe().expect("current executable path invalid"); 372 | cmd.env("RUSTC_WRAPPER", path); 373 | if verbose { 374 | cmd.env("CALLGRAPH_VERBOSE", ""); // this makes `inside_cargo_rustc` verbose. 375 | eprintln!("+ {:?}", cmd); 376 | } 377 | 378 | let mut child = cmd.spawn().expect("could not run cargo check"); 379 | // 1 hour timeout 380 | match child 381 | .wait_timeout(Duration::from_secs(60 * 60)) 382 | .expect("failed to wait for subprocess") 383 | { 384 | Some(exit_status) => { 385 | if !exit_status.success() { 386 | show_error("Finished with non-zero exit code"); 387 | } 388 | } 389 | None => { 390 | child.kill().expect("failed to kill subprocess"); 391 | child.wait().expect("failed to wait for subprocess"); 392 | show_error("Killed due to timeout"); 393 | } 394 | }; 395 | } 396 | } 397 | 398 | fn inside_cargo_rustc() { 399 | /// Determines if we are being invoked (as rustc) to build a crate for 400 | /// the "target" architecture, in contrast to the "host" architecture. 401 | /// Host crates are for build scripts and proc macros and still need to 402 | /// be built like normal; target crates need to be built for or interpreted 403 | /// by Callgraph. 404 | /// 405 | /// Currently, we detect this by checking for "--target=", which is 406 | /// never set for host crates. This matches what rustc bootstrap does, 407 | /// which hopefully makes it "reliable enough". This relies on us always 408 | /// invoking cargo itself with `--target`, which `in_cargo_callgraph` ensures. 409 | fn contains_target_flag() -> bool { 410 | get_arg_flag_value("--target").is_some() 411 | } 412 | 413 | /// Returns whether we are building the target crate. 414 | /// Cargo passes the file name as a relative address when building the local crate, 415 | /// such as `crawl/src/bin/unsafe-counter.rs` when building the target crate. 416 | /// This might not be a stable behavior, but let's rely on this for now. 417 | fn is_target_crate() -> bool { 418 | let entry_path_arg = match get_first_arg_with_rs_suffix() { 419 | Some(arg) => arg, 420 | None => return false, 421 | }; 422 | let entry_path: &Path = entry_path_arg.as_ref(); 423 | 424 | entry_path.is_relative() 425 | } 426 | 427 | fn is_crate_type_lib() -> bool { 428 | any_arg_flag("--crate-type", TargetKind::is_lib_str) 429 | } 430 | 431 | fn run_command(mut cmd: Command) { 432 | // Run it. 433 | let verbose = std::env::var_os("CALLGRAPH_VERBOSE").is_some(); 434 | if verbose { 435 | eprintln!("+ {:?}", cmd); 436 | } 437 | 438 | match cmd.status() { 439 | Ok(exit) => { 440 | if !exit.success() { 441 | std::process::exit(exit.code().unwrap_or(42)); 442 | } 443 | } 444 | Err(e) => panic!("error running {:?}:\n{:?}", cmd, e), 445 | } 446 | } 447 | 448 | // TODO: Miri sets custom sysroot here, check if it is needed for us (CALLGRAPH-30) 449 | 450 | let is_direct_target = contains_target_flag() && is_target_crate(); 451 | let mut is_additional_target = false; 452 | 453 | if is_direct_target || is_additional_target { 454 | let mut cmd = Command::new(find_callgraph()); 455 | cmd.args(std::env::args().skip(2)); // skip `cargo-callgraph rustc` 456 | 457 | // This is the local crate that we want to analyze with Callgraph. 458 | // (Testing `target_crate` is needed to exclude build scripts.) 459 | // We deserialize the arguments that are meant for Callgraph from the special 460 | // environment variable "CALLGRAPH_ARGS", and feed them to the 'callgraph' binary. 461 | // 462 | // `env::var` is okay here, well-formed JSON is always UTF-8. 463 | let magic = std::env::var("CALLGRAPH_ARGS").expect("missing CALLGRAPH_ARGS"); 464 | let callgraph_args: Vec = 465 | serde_json::from_str(&magic).expect("failed to deserialize CALLGRAPH_ARGS"); 466 | cmd.args(callgraph_args); 467 | 468 | run_command(cmd); 469 | } 470 | 471 | // Callgraph does not build anything. 472 | // We need to run rustc (or sccache) to build dependencies. 473 | if !is_direct_target || is_crate_type_lib() { 474 | let cmd = match which::which("sccache") { 475 | Ok(sccache_path) => { 476 | let mut cmd = Command::new(&sccache_path); 477 | // ["cargo-callgraph", "rustc", ...] 478 | cmd.args(std::env::args().skip(1)); 479 | cmd 480 | } 481 | Err(_) => { 482 | // sccache was not found, use vanilla rustc 483 | let mut cmd = Command::new("rustc"); 484 | // ["cargo-callgraph", "rustc", ...] 485 | cmd.args(std::env::args().skip(2)); 486 | cmd 487 | } 488 | }; 489 | 490 | run_command(cmd); 491 | } 492 | } 493 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![feature(rustc_private)] 2 | 3 | extern crate rustc_driver; 4 | extern crate rustc_interface; 5 | extern crate rustc_hir; 6 | extern crate rustc_middle; 7 | extern crate rustc_span; 8 | extern crate rustc_version; 9 | extern crate cargo_metadata; 10 | 11 | use rustc_middle::ty::TyCtxt; 12 | 13 | mod visitor; 14 | 15 | /// Returns the "default sysroot" that Callgraph will use if no `--sysroot` flag is set. 16 | /// Should be a compile-time constant. 17 | pub fn compile_time_sysroot() -> Option { 18 | // option_env! is replaced to a constant at compile time 19 | if option_env!("RUSTC_STAGE").is_some() { 20 | // This is being built as part of rustc, and gets shipped with rustup. 21 | // We can rely on the sysroot computation in librustc. 22 | return None; 23 | } 24 | 25 | // For builds outside rustc, we need to ensure that we got a sysroot 26 | // that gets used as a default. The sysroot computation in librustc would 27 | // end up somewhere in the build dir. 28 | // Taken from PR . 29 | let home = option_env!("RUSTUP_HOME").or(option_env!("MULTIRUST_HOME")); 30 | let toolchain = option_env!("RUSTUP_TOOLCHAIN").or(option_env!("MULTIRUST_TOOLCHAIN")); 31 | Some(match (home, toolchain) { 32 | (Some(home), Some(toolchain)) => format!("{}/toolchains/{}", home, toolchain), 33 | _ => option_env!("RUST_SYSROOT") 34 | .expect("To build Callgraph without rustup, set the `RUST_SYSROOT` env var at build time") 35 | .to_owned(), 36 | }) 37 | } 38 | 39 | pub fn analyze(&tcx: &TyCtxt<'_>) { 40 | let mut visitor = visitor::CallgraphVisitor::new(&tcx); 41 | tcx.hir().visit_all_item_likes_in_crate(&mut visitor); 42 | 43 | visitor.dump(); 44 | } 45 | -------------------------------------------------------------------------------- /src/visitor.rs: -------------------------------------------------------------------------------- 1 | use rustc_hir::HirId; 2 | use rustc_hir::def_id::DefId; 3 | use rustc_middle::ty::TyCtxt; 4 | use rustc_middle::ty::ParamEnvAnd; 5 | use std::collections::{HashMap, HashSet}; 6 | use rustc_hir::intravisit; 7 | use rustc_middle::hir::nested_filter; 8 | use rustc_span::Span; 9 | 10 | macro_rules! skip_generated_code { 11 | ($span: expr) => { 12 | if $span.from_expansion() || $span.is_dummy() { 13 | return; 14 | } 15 | }; 16 | } 17 | 18 | // Backup self.cur_fn, set cur_fn to id, continue to walk the AST by executing 19 | // $walk, then restore self.cur_fn. 20 | macro_rules! push_walk_pop { 21 | ($this: expr, $id: expr, $walk: expr) => {{ 22 | let prev_fn = $this.cur_fn; 23 | $this.cur_fn = Some($id); 24 | $walk; 25 | $this.cur_fn = prev_fn; 26 | }}; 27 | } 28 | 29 | #[derive(Hash, PartialEq, Eq, Debug)] 30 | struct Call { 31 | // the call expression 32 | call_expr: HirId, 33 | call_expr_span: Span, 34 | // possible enclosing function 35 | caller: Option, 36 | caller_span: Option, 37 | // call target 38 | callee: DefId, 39 | callee_span: Span, 40 | } 41 | 42 | pub struct CallgraphVisitor<'tcx> { 43 | // type context 44 | tcx: TyCtxt<'tcx>, 45 | 46 | // free functions 47 | functions: HashSet<(DefId, Span)>, 48 | // trait method declarations without default implementation 49 | method_decls: HashSet, 50 | // map decls to impls 51 | method_impls: HashMap>, 52 | 53 | // static calls 54 | static_calls: HashSet, 55 | // dynamic calls 56 | dynamic_calls: HashSet, 57 | 58 | // tracks the current function we're in during AST walk 59 | cur_fn: Option, 60 | } 61 | 62 | impl<'tcx> CallgraphVisitor<'tcx> { 63 | pub fn new(tcx: &TyCtxt<'tcx>) -> CallgraphVisitor<'tcx> { 64 | CallgraphVisitor { 65 | tcx: *tcx, 66 | functions: HashSet::new(), 67 | method_decls: HashSet::new(), 68 | method_impls: HashMap::new(), 69 | static_calls: HashSet::new(), 70 | dynamic_calls: HashSet::new(), 71 | cur_fn: None, 72 | } 73 | } 74 | 75 | pub fn dump(&self) { 76 | dbg!(&self.functions); 77 | dbg!(&self.method_decls); 78 | dbg!(&self.method_impls); 79 | dbg!(&self.static_calls); 80 | dbg!(&self.dynamic_calls); 81 | } 82 | } 83 | 84 | impl<'tcx> intravisit::Visitor<'tcx> for CallgraphVisitor<'tcx> { 85 | 86 | type NestedFilter = nested_filter::OnlyBodies; 87 | 88 | fn nested_visit_map(&mut self) -> Self::Map { 89 | self.tcx.hir() 90 | } 91 | 92 | fn visit_expr(&mut self, expr: &'tcx rustc_hir::Expr) { 93 | skip_generated_code!(expr.span); 94 | 95 | let hir_id = expr.hir_id; 96 | match expr.kind { 97 | rustc_hir::ExprKind::Call( 98 | rustc_hir::Expr{ 99 | kind: rustc_hir::ExprKind::Path(ref qpath), 100 | .. 101 | }, _) => { 102 | if let rustc_hir::QPath::Resolved(_, p) = qpath { 103 | if let rustc_hir::def::Res::Def(_, def_id) = p.res { 104 | self.static_calls.insert(Call { 105 | call_expr: hir_id, 106 | call_expr_span: expr.span, 107 | caller: self.cur_fn, 108 | caller_span: None, 109 | callee: def_id, 110 | callee_span: p.span, 111 | }); 112 | } 113 | } 114 | }, 115 | rustc_hir::ExprKind::MethodCall(_, _, _, _) => { 116 | let o_def_id = hir_id.owner; 117 | let typeck_tables = self.tcx.typeck(o_def_id); 118 | let substs = typeck_tables.node_args(hir_id); 119 | let method_id = typeck_tables.type_dependent_def_id(hir_id).expect("fail"); 120 | let param_env = self.tcx.param_env(method_id); 121 | if let Ok(Some(inst)) = 122 | self.tcx.resolve_instance_raw(ParamEnvAnd{param_env, value: (method_id, substs)}) 123 | { 124 | let res_def_id = inst.def_id(); 125 | match self.tcx.hir().get_if_local(res_def_id) { 126 | Some(rustc_hir::Node::TraitItem(rustc_hir::TraitItem{span, ..})) => { 127 | // dynamic calls resolve only to the trait method decl 128 | self.dynamic_calls.insert(Call { 129 | call_expr: hir_id, 130 | call_expr_span: expr.span, 131 | caller: self.cur_fn, 132 | caller_span: None, 133 | callee: res_def_id, 134 | callee_span: *span, 135 | }); 136 | } 137 | Some(rustc_hir::Node::ImplItem(rustc_hir::ImplItem{span, ..})) | 138 | Some(rustc_hir::Node::Item(rustc_hir::Item{span, ..})) | 139 | Some(rustc_hir::Node::ForeignItem(rustc_hir::ForeignItem{span, ..})) => { 140 | // calls for which the receiver's type can be resolved 141 | self.static_calls.insert(Call { 142 | call_expr: hir_id, 143 | call_expr_span: expr.span, 144 | caller: self.cur_fn, 145 | caller_span: None, 146 | callee: res_def_id, 147 | callee_span: *span, 148 | }); 149 | }, 150 | None => (), 151 | _ => todo!() 152 | }; 153 | } 154 | }, 155 | _ => {}, 156 | } 157 | // traverse 158 | intravisit::walk_expr(self, expr); 159 | } 160 | 161 | fn visit_item(&mut self, item: &'tcx rustc_hir::Item) { 162 | skip_generated_code!(item.span); 163 | 164 | let hir_id = item.hir_id(); 165 | if let rustc_hir::ItemKind::Fn(_, _, _) = item.kind { 166 | let def_id = hir_id.owner.to_def_id(); 167 | self.functions.insert((def_id, item.span)); 168 | 169 | push_walk_pop!(self, def_id, intravisit::walk_item(self, item)); 170 | 171 | return; 172 | } 173 | // traverse 174 | intravisit::walk_item(self, item) 175 | } 176 | 177 | fn visit_trait_item(&mut self, ti: &'tcx rustc_hir::TraitItem) { 178 | skip_generated_code!(ti.span); // TODO ?do we want this 179 | 180 | let hir_id = ti.hir_id(); 181 | let def_id = hir_id.owner.to_def_id(); 182 | 183 | match ti.kind { 184 | rustc_hir::TraitItemKind::Fn(_, rustc_hir::TraitFn::Required(_)) => { 185 | // a method declaration 186 | self.method_decls.insert(def_id); 187 | self.method_impls.insert(def_id, vec![]); 188 | } 189 | rustc_hir::TraitItemKind::Fn(_, rustc_hir::TraitFn::Provided(_)) => { 190 | // a method decl and def 191 | self.method_decls.insert(def_id); 192 | self.functions.insert((def_id, ti.span)); 193 | self.method_impls.entry(def_id).or_default().push(def_id); 194 | 195 | push_walk_pop!(self, def_id, intravisit::walk_trait_item(self, ti)); 196 | 197 | return; 198 | } 199 | _ => {} 200 | } 201 | 202 | // traverse 203 | intravisit::walk_trait_item(self, ti) 204 | } 205 | 206 | // self.tcx.hir().hir_to_pretty_string(ty.hir_id) 207 | 208 | fn visit_impl_item(&mut self, ii: &'tcx rustc_hir::ImplItem) { 209 | skip_generated_code!(ii.span); 210 | 211 | let hir_id = ii.hir_id(); 212 | let def_id = hir_id.owner.to_def_id(); 213 | 214 | if let rustc_hir::ImplItemKind::Fn(..) = ii.kind { 215 | self.functions.insert((def_id, ii.span)); 216 | 217 | // store link to decl 218 | let mut decl_id = None; 219 | if let Some(impl_id) = self.tcx.impl_of_method(def_id) { 220 | if let Some(rustc_hir::Node::Item(item)) = self.tcx.hir().get_if_local(impl_id) { 221 | if let rustc_hir::ItemKind::Impl(..) = item.kind { 222 | // the next one filters methods that are just associated 223 | // and do not belong to a struct 224 | if let Some(trait_def_id) = self.tcx.trait_id_of_impl(impl_id) { 225 | let item = self.tcx 226 | .associated_items(trait_def_id) 227 | .filter_by_name_unhygienic(ii.ident.name) 228 | .next(); // There should ideally be only one item matching the name 229 | if let Some(item) = item { 230 | decl_id = Some(item.def_id); 231 | }; 232 | } 233 | } 234 | } 235 | } 236 | 237 | if let Some(decl_def_id) = decl_id { 238 | self.method_impls 239 | .entry(decl_def_id) 240 | .or_default() 241 | .push(def_id); 242 | } 243 | 244 | push_walk_pop!(self, def_id, intravisit::walk_impl_item(self, ii)); 245 | 246 | return; 247 | } 248 | 249 | // traverse 250 | intravisit::walk_impl_item(self, ii) 251 | } 252 | } 253 | --------------------------------------------------------------------------------