├── .gitignore ├── .travis.yml ├── Cargo.lock ├── Cargo.toml ├── LICENSE-APACHE-2.0 ├── LICENSE-MIT ├── README.md ├── build_scripts └── clean.sh ├── lib ├── callgraph │ ├── Cargo.toml │ └── src │ │ ├── binary_read │ │ └── mod.rs │ │ ├── callgraph │ │ ├── lea_dynamic_calls.rs │ │ ├── mod.rs │ │ └── static_calls.rs │ │ ├── crate_utils.rs │ │ ├── dwarf_utils.rs │ │ ├── lib.rs │ │ └── parse │ │ └── mod.rs ├── panic_analysis │ ├── Cargo.toml │ ├── src │ │ ├── binary │ │ │ └── mod.rs │ │ ├── filter │ │ │ ├── mod.rs │ │ │ ├── panic_filter.rs │ │ │ └── whitelist_filter.rs │ │ ├── graph_output │ │ │ └── mod.rs │ │ ├── lib.rs │ │ ├── marker │ │ │ ├── analysis_target.rs │ │ │ ├── entry_point.rs │ │ │ ├── function_whitelist.rs │ │ │ ├── mod.rs │ │ │ └── panic.rs │ │ ├── panic_calls │ │ │ ├── mod.rs │ │ │ └── panic_message.rs │ │ ├── patterns │ │ │ ├── direct_panic.rs │ │ │ ├── function_panic.rs │ │ │ ├── message_panic.rs │ │ │ └── mod.rs │ │ └── test_utils.rs │ └── tests │ │ ├── function_whitelist.rs │ │ ├── libcalls.rs │ │ ├── panic_message.rs │ │ ├── recognize-arithmetic.rs │ │ ├── recognize_direct.rs │ │ ├── recognize_indexing.rs │ │ └── recognize_unwrap.rs └── test_common │ ├── Cargo.toml │ ├── build.rs │ └── src │ └── lib.rs ├── src ├── cmd_args.rs ├── config_file.rs ├── main.rs └── output.rs ├── test_subjects ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── README.md ├── arithmetic │ ├── Cargo.toml │ └── src │ │ └── main.rs ├── binary_search │ ├── Cargo.toml │ └── src │ │ └── main.rs ├── capturing_closure_invocation │ ├── Cargo.toml │ └── src │ │ └── main.rs ├── dep │ ├── Cargo.toml │ └── src │ │ └── lib.rs ├── direct │ ├── Cargo.toml │ └── src │ │ └── main.rs ├── empty │ ├── Cargo.toml │ └── src │ │ └── main.rs ├── hello_world │ ├── Cargo.toml │ └── src │ │ └── main.rs ├── indexing │ ├── Cargo.toml │ └── src │ │ └── main.rs ├── indirect │ ├── Cargo.toml │ └── src │ │ └── main.rs ├── lib_calls │ ├── Cargo.toml │ └── src │ │ └── main.rs ├── multi_dep │ ├── Cargo.toml │ └── src │ │ └── main.rs ├── trait_invocation │ ├── Cargo.toml │ └── src │ │ └── main.rs └── unwrap │ ├── Cargo.toml │ └── src │ └── main.rs ├── test_subjects_lib ├── Cargo.toml └── src │ ├── arithmetic.rs │ ├── box_traits.rs │ ├── impl_traits.rs │ ├── inline.rs │ ├── lambda_structs.rs │ ├── lib.rs │ ├── panic_types.rs │ ├── reference_calls.rs │ ├── same_vtable.rs │ ├── struct_calls.rs │ ├── trait_dynamic_calls.rs │ ├── trait_similar_names.rs │ ├── trait_simple_calls.rs │ └── unwrap_calls.rs └── test_subjects_stable_rustc ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── README.md ├── hello_world ├── Cargo.toml └── src │ └── main.rs └── threads ├── Cargo.toml └── src └── main.rs /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | /target 3 | **/*.rs.bk 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: rust 2 | 3 | rust: 4 | - stable 5 | - beta 6 | - nightly 7 | 8 | matrix: 9 | allow_failures: 10 | - rust: nightly 11 | fast_finish: true 12 | 13 | cache: cargo 14 | 15 | script: 16 | - cargo build --all 17 | - cargo test --all 18 | - rustup show 19 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = [ 3 | "lib/callgraph", 4 | "lib/panic_analysis", 5 | "lib/test_common", 6 | ] 7 | 8 | [package] 9 | name = "rustig" 10 | description = "A tool to detect code paths leading to Rust's panic handler" 11 | version = "0.2.0" 12 | license = "MIT OR Apache-2.0" 13 | keywords = ["cli", "compilation", "proof"] 14 | homepage = "https://github.com/Technolution/rustig" 15 | repository = "https://github.com/Technolution/rustig" 16 | documentation = "https://github.com/Technolution/rustig" 17 | authors = [ 18 | "Bart van Schaick ", 19 | "Dominique van Cuilenborg ", 20 | "Fabian Stelmach ", 21 | "Aron Zwaan ", 22 | "Erwin Gribnau " 23 | ] 24 | 25 | [dependencies] 26 | clap = "2.31.2" 27 | callgraph = { path = "lib/callgraph", version = "0.1.0" } 28 | panic_analysis = { path = "lib/panic_analysis", version = "0.1.0" } 29 | toml = "0.4.6" 30 | serde = "1.0.64" 31 | serde_derive = "1.0.64" 32 | error-chain = "0.12.0" 33 | serde_json = "1.0.22" 34 | 35 | [dev-dependencies] 36 | assert_cli = "0.5" 37 | test_common = { path = "lib/test_common", version = "0.1.0" } 38 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Technolution BV 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /build_scripts/clean.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # The purpose of this script is to clean not only the 'rustig' software, but also 4 | # clean out all test subjects. Running 'cargo clean' on the top-level directory does 5 | # not do this. 6 | cargo clean 7 | cd test_subjects && cargo clean && cd .. 8 | cd test_subjects_stable_rustc && cargo clean && cd .. 9 | -------------------------------------------------------------------------------- /lib/callgraph/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "callgraph" 3 | version = "0.1.0" 4 | authors = [ 5 | "Bart van Schaick ", 6 | "Dominique van Cuilenborg ", 7 | "Fabian Stelmach ", 8 | "Aron Zwaan ", 9 | "Erwin Gribnau " 10 | ] 11 | workspace = "../.." 12 | 13 | [dependencies] 14 | addr2line = "0.6.0" 15 | petgraph = "0.4.12" 16 | object = "0.7.0" 17 | goblin = "0.0.13" 18 | gimli = "0.15.0" 19 | capstone = { git = "https://github.com/capstone-rust/capstone-rs.git", rev = "dc10578aa27414afc93f0a87959b2f0c22dc66c3" } 20 | elf = "0.0.10" 21 | error-chain = "0.12.0" 22 | fallible-iterator = "0.1.2" 23 | byteorder = "1.2.3" 24 | 25 | [dev-dependencies] 26 | test_common = { path = "../../lib/test_common" } 27 | 28 | -------------------------------------------------------------------------------- /lib/callgraph/src/binary_read/mod.rs: -------------------------------------------------------------------------------- 1 | // (C) COPYRIGHT 2018 TECHNOLUTION BV, GOUDA NL 2 | 3 | // Licensed under the Apache License, Version 2.0 or the MIT license 5 | // , at your 6 | // option. This file may not be copied, modified, or distributed 7 | // except according to those terms. 8 | 9 | extern crate std; 10 | 11 | use Binary; 12 | use CallGraphOptions; 13 | 14 | use errors::*; 15 | use std::io::Read; 16 | 17 | /// Trait marking objects that can read a binary file, and return the content as a `Vec`. 18 | pub trait BinaryReader { 19 | fn read<'a>(&self, binary: &'a Binary) -> Result<(Vec)>; 20 | } 21 | 22 | /// Default implementation of `BinaryReader`, that reads the file, without any extraordinary processing. 23 | struct DefaultBinaryReader; 24 | 25 | impl BinaryReader for DefaultBinaryReader { 26 | fn read<'a>(&self, binary: &'a Binary) -> Result<(Vec)> { 27 | let mut file_content = Vec::new(); 28 | let mut file = std::fs::File::open(binary.path) 29 | .chain_err(|| ErrorKind::IOError(binary.path.to_str().unwrap().to_string()))?; 30 | 31 | file.read_to_end(&mut file_content) 32 | .chain_err(|| ErrorKind::ReadError(binary.path.to_str().unwrap().to_string()))?; 33 | 34 | Ok(file_content) 35 | } 36 | } 37 | 38 | /// Function that returns a `BinaryReader` implementation based on the passed parameters. 39 | pub fn get_reader(_options: &CallGraphOptions) -> Box { 40 | Box::new(DefaultBinaryReader) 41 | } 42 | 43 | #[cfg(test)] 44 | mod test { 45 | extern crate test_common; 46 | 47 | use self::test_common::TestSubjectType; 48 | use super::*; 49 | 50 | /// Test whether the correct number of bytes is read by the binary reader. 51 | #[test] 52 | fn test_binary_reader() { 53 | let path = 54 | test_common::get_test_subject_path("hello_world", &TestSubjectType::DebugStableRustc); 55 | 56 | let binary = Binary { path: &path }; 57 | 58 | let vec = DefaultBinaryReader.read(&binary); 59 | 60 | assert!(!vec.unwrap().is_empty()) 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /lib/callgraph/src/callgraph/static_calls.rs: -------------------------------------------------------------------------------- 1 | // (C) COPYRIGHT 2018 TECHNOLUTION BV, GOUDA NL 2 | 3 | // Licensed under the Apache License, Version 2.0 or the MIT license 5 | // , at your 6 | // option. This file may not be copied, modified, or distributed 7 | // except according to those terms. 8 | 9 | extern crate addr2line; 10 | extern crate fallible_iterator; 11 | extern crate gimli; 12 | extern crate object; 13 | extern crate petgraph; 14 | 15 | use Context; 16 | use InlineFunctionFrame; 17 | use Invocation; 18 | use InvocationType; 19 | use Procedure; 20 | 21 | use callgraph::fallible_iterator::FallibleIterator; 22 | use callgraph::InvocationFinder; 23 | 24 | use std::cell::RefCell; 25 | use std::collections::hash_map::RandomState; 26 | use std::collections::HashMap; 27 | use std::rc::Rc; 28 | 29 | use capstone::*; 30 | 31 | use callgraph::CompilationInfo; 32 | use petgraph::stable_graph::{NodeIndex, StableGraph}; 33 | use petgraph::Directed; 34 | 35 | /// Enum indicating how a call was encoded in assembly. 36 | #[derive(Debug)] 37 | enum CallType { 38 | StaticCall { insn: Insn, target: i64 }, 39 | StaticJump { insn: Insn, target: i64 }, 40 | } 41 | 42 | impl CallType { 43 | fn invocation_type(&self) -> InvocationType { 44 | match self { 45 | CallType::StaticJump { .. } => InvocationType::Jump, 46 | CallType::StaticCall { .. } => InvocationType::Direct, 47 | } 48 | } 49 | } 50 | 51 | pub struct StaticCallInvocationFinder; 52 | 53 | impl InvocationFinder for StaticCallInvocationFinder { 54 | fn find_invocations( 55 | &self, 56 | graph: &mut StableGraph< 57 | Rc>>, 58 | Rc>>, 59 | Directed, 60 | u32, 61 | >, 62 | proc_index: &mut HashMap, RandomState>, 63 | call_index: &mut HashMap, RandomState>, 64 | ctx: &Context, 65 | compilation_info: CompilationInfo, 66 | ) { 67 | let node_indices: Vec<_> = graph.node_indices().collect(); 68 | node_indices.iter() 69 | .map(|idx| parse_calls(&graph[*idx].borrow().disassembly, &ctx.capstone)) 70 | .fold(vec!(), |mut vec, mut elem| { 71 | vec.append(&mut elem); 72 | vec 73 | }) 74 | .iter() 75 | // Map `CallType` to (origin, destination, invocation type, locations) quadruple. 76 | .filter_map(|call_type| { 77 | let invocation_type = call_type.invocation_type(); 78 | match call_type { 79 | CallType::StaticCall { insn, target } | CallType::StaticJump { insn, target } => { 80 | let origin = &call_index[&insn.address()]; 81 | let destination = proc_index.get(&(*target as u64))?; 82 | 83 | let frames = ctx.file_context.find_frames(insn.address()) 84 | .unwrap_or_else(|_| panic!("Creating iterator over static function frames of the given virtual memory address: {} failed", insn.address())) 85 | .iterator() 86 | .filter_map(|frame_res| frame_res.ok()) 87 | .map(|frame| InlineFunctionFrame::convert_frame(&frame, compilation_info.compilation_dirs, compilation_info.rust_version.to_owned())) 88 | .collect(); 89 | 90 | Some((origin, destination, invocation_type, frames, insn.address())) 91 | } 92 | } 93 | }) 94 | // Add edges for all invocations 95 | .for_each(|(origin, destination, invocation_type, frames, instruction_address)| { 96 | // use addrs2line with call_instr_addr 97 | graph.add_edge( 98 | *origin, 99 | *destination, 100 | Rc::new(RefCell::new(Invocation { 101 | invocation_type, 102 | instruction_address, 103 | frames, 104 | attributes: I::default() 105 | }))); 106 | }); 107 | } 108 | } 109 | /// Transform collection of instructions to their corresponding call types. 110 | fn parse_calls(instructions: &Instructions, cs: &Capstone) -> Vec { 111 | instructions 112 | .iter() 113 | .flat_map(|insn| get_call_type(insn, cs)) 114 | .collect() 115 | } 116 | 117 | /// Extract the call type for a specific instruction. 118 | fn get_call_type(insn: Insn, cs: &Capstone) -> Option { 119 | let jump_group_id = InsnGroupId(1); // Group 1 is jump 120 | let call_group_id = InsnGroupId(2); // Group 2 is call 121 | 122 | let is_call = cs.insn_group_ids(&insn) 123 | .unwrap() 124 | .any(|id| id == call_group_id); 125 | let is_jump = cs.insn_group_ids(&insn) 126 | .unwrap() 127 | .any(|id| id == jump_group_id); 128 | 129 | // Return no call type if instruction is neither a jump nor a call 130 | if !is_call && !is_jump { 131 | return None; 132 | } 133 | 134 | let operands = { 135 | let details = cs.insn_detail(&insn).unwrap(); 136 | let operands = details.arch_detail().operands(); 137 | 138 | if operands.len() != 1 { 139 | panic!( 140 | "Call instruction should have one operand, but {} were found. Instruction: {:?}. Details: {:?}", 141 | operands.len(), 142 | insn, 143 | details 144 | ); 145 | } 146 | operands 147 | }; 148 | 149 | let operand = match operands[0] { 150 | arch::ArchOperand::X86Operand(ref operand) => operand, 151 | _ => panic!("Only x86 instructions are supported"), 152 | }; 153 | 154 | match operand.op_type { 155 | arch::x86::X86OperandType::Imm(target) if is_call => { 156 | Some(CallType::StaticCall { insn, target }) 157 | } 158 | arch::x86::X86OperandType::Imm(target) => Some(CallType::StaticJump { insn, target }), 159 | arch::x86::X86OperandType::Reg(RegId(_)) | arch::x86::X86OperandType::Mem(_) => None, 160 | arch::x86::X86OperandType::Fp(_) => { 161 | panic!("Invalid call operand: call to a float is not a valid call") 162 | } 163 | arch::x86::X86OperandType::Invalid => panic!("Invalid call operand"), 164 | } 165 | } 166 | 167 | #[cfg(test)] 168 | mod test { 169 | extern crate capstone; 170 | extern crate elf; 171 | extern crate gimli; 172 | extern crate object; 173 | extern crate test_common; 174 | 175 | use super::*; 176 | 177 | use capstone::arch::BuildsCapstone; 178 | use capstone::prelude::*; 179 | use capstone::Capstone; 180 | 181 | /// Verify non-calls do not create CallTypes 182 | #[test] 183 | pub fn test_invalid_calls() { 184 | let cs = Capstone::new() 185 | .x86() 186 | .mode(arch::x86::ArchMode::Mode64) 187 | .syntax(arch::x86::ArchSyntax::Att) 188 | .detail(true) 189 | .build() 190 | .unwrap(); 191 | 192 | let assembly = &[25, 50, 75, 100]; 193 | 194 | let disassembly = { 195 | cs.disasm_all(assembly, 0) 196 | .expect("Failed to disassemble test data") 197 | }; 198 | 199 | let call_types = parse_calls(&disassembly, &cs); 200 | 201 | assert_eq!(call_types.len(), 0); 202 | } 203 | } 204 | -------------------------------------------------------------------------------- /lib/callgraph/src/crate_utils.rs: -------------------------------------------------------------------------------- 1 | // (C) COPYRIGHT 2018 TECHNOLUTION BV, GOUDA NL 2 | 3 | // Licensed under the Apache License, Version 2.0 or the MIT license 5 | // , at your 6 | // option. This file may not be copied, modified, or distributed 7 | // except according to those terms. 8 | 9 | use addr2line::Frame; 10 | 11 | use dwarf_utils; 12 | 13 | use Context; 14 | use Crate; 15 | 16 | use gimli; 17 | use gimli::DebuggingInformationEntry; 18 | use gimli::EndianBuf; 19 | use gimli::LittleEndian; 20 | use gimli::RunTimeEndian; 21 | 22 | /// Function that returns a `Crate` instance from the compilation dir. 23 | /// Usually, for an external crate, the direactory has the format '/path/to/checkout/- 24 | /// In that case, `Crate { name: "", version: Some("") }` is returned. 25 | /// Some special cases are: 26 | /// - When there is no numeric char after the crate name, it is assumed the version in not known. 27 | /// In that case, the `version` field is `None`. 28 | /// - When the checkout dir is '/checkout/src/`, then it is stdlib code 29 | /// In that case `Crate { name: "stdlib", version: Some("") }` is returned 30 | /// 31 | pub fn get_crate_from_comp_dir(comp_dir: Option<&str>, rust_version: String) -> Crate { 32 | let comp_dir = match comp_dir { 33 | Some(dir) => dir, 34 | None => { 35 | return Crate { 36 | name: "".to_string(), 37 | version: None, 38 | } 39 | } 40 | }; 41 | 42 | // In /checkout/src, the standard library is stored. 43 | // So return stdlib-rust-version 44 | if comp_dir == "/checkout/src" { 45 | return Crate { 46 | name: "stdlib".to_string(), 47 | version: Some(rust_version), 48 | }; 49 | } 50 | 51 | let crate_name_version = comp_dir.rsplit('/').next().expect("No / in path"); 52 | let mut crate_name_split: Vec<_> = crate_name_version.rsplit('-').collect(); 53 | 54 | // Splitting a string always returns at least one element, so we can safely assert this. 55 | assert!(!crate_name_split.is_empty()); 56 | 57 | let (name, version) = 58 | if crate_name_split.len() == 1 || !crate_name_version.chars().any(|x| x.is_numeric()) { 59 | crate_name_split.reverse(); 60 | (crate_name_split.join("-").to_string(), None) 61 | } else { 62 | let version = crate_name_split.remove(0); 63 | crate_name_split.reverse(); 64 | (crate_name_split.join("-"), Some(version.to_string())) 65 | }; 66 | 67 | Crate { name, version } 68 | } 69 | 70 | /// Gets the crate details for a compilation unit 71 | pub fn get_crate_details( 72 | _address: u64, 73 | defining_file: Option<&str>, 74 | cu_die: &DebuggingInformationEntry>, 75 | ctx: &Context, 76 | compilation_unit_dirs: &[&str], 77 | ) -> Crate { 78 | let producer = 79 | dwarf_utils::get_attr_string_value(&cu_die, gimli::DW_AT_producer, &ctx.dwarf_strings) 80 | .expect("No producer for compilation unit"); 81 | 82 | // Assume producer is in a format like 'clang LLVM (rustc version 1.26.0 (a77568041 2018-05-07))' 83 | let rust_version = producer 84 | .rsplit("rustc version") 85 | .next() 86 | .expect("Unexpected producer string format") 87 | .trim() 88 | .split_whitespace() 89 | .next() 90 | .expect("Unexpected producer string format") 91 | .to_string(); 92 | 93 | // Find a match between the compilation unit directories and the file. 94 | // If no match is found, set crate name to "Unknown". 95 | let comp_dir = match defining_file { 96 | Some(defining_file) => compilation_unit_dirs 97 | .iter() 98 | .find(|compilation_unit| defining_file.starts_with(**compilation_unit)) 99 | .map(|string| *string), 100 | None => None, 101 | }; 102 | 103 | get_crate_from_comp_dir(comp_dir, rust_version) 104 | } 105 | 106 | /// Returns the crate for inlined functions 107 | pub fn get_crate_for_inlined_functions( 108 | frame: &Frame>, 109 | compilation_dirs: &[&str], 110 | rust_version: String, 111 | ) -> Crate { 112 | let default_crate = Crate { 113 | name: "".to_string(), 114 | version: None, 115 | }; 116 | 117 | if frame.location.is_none() { 118 | return default_crate; 119 | } 120 | 121 | // Safe unwrap: is_none check before 122 | let location = frame.location.as_ref().unwrap(); 123 | 124 | if location.file.is_none() { 125 | return default_crate; 126 | } 127 | 128 | // Safe unwrap: is_none check before 129 | let file_path = location.file.as_ref().unwrap(); 130 | 131 | let frame_comp_dir = compilation_dirs 132 | .iter() 133 | .find(|comp_dir| file_path.starts_with(comp_dir)); 134 | get_crate_from_comp_dir(frame_comp_dir.map(|x| x.to_owned()), rust_version) 135 | } 136 | 137 | #[cfg(test)] 138 | mod tests { 139 | use super::*; 140 | 141 | /// Test `get_crate_from_comp_dir` with an usual 3-rd party crate name 142 | #[test] 143 | pub fn test_get_crate_from_comp_dir() { 144 | let crt = 145 | get_crate_from_comp_dir(Some("/home/test/crate-name-0.1.2.2"), "1.26".to_string()); 146 | 147 | assert_eq!(crt.name, "crate-name"); 148 | assert_eq!(crt.version, Some("0.1.2.2".to_string())); 149 | } 150 | 151 | /// Test `get_crate_from_comp_dir` for an stdlib compilation unit 152 | #[test] 153 | pub fn test_get_crate_stdlib() { 154 | let crt = get_crate_from_comp_dir(Some("/checkout/src"), "1.26".to_string()); 155 | 156 | assert_eq!(crt.name, "stdlib"); 157 | assert_eq!(crt.version, Some("1.26".to_string())); 158 | } 159 | 160 | /// Test `get_crate_from_comp_dir` when no version is specified. 161 | #[test] 162 | pub fn test_get_crate_no_version() { 163 | let crt = get_crate_from_comp_dir(Some("/home/test/crate-name"), "1.26".to_string()); 164 | 165 | assert_eq!(crt.name, "crate-name"); 166 | assert_eq!(crt.version, None); 167 | } 168 | /// Test `get_crate_from_comp_dir` when "Unknown" is used. 169 | #[test] 170 | pub fn test_get_crate_unknown() { 171 | let crt = get_crate_from_comp_dir(None, "1.26".to_string()); 172 | 173 | assert_eq!(crt.name, ""); 174 | assert_eq!(crt.version, None); 175 | } 176 | } 177 | -------------------------------------------------------------------------------- /lib/callgraph/src/dwarf_utils.rs: -------------------------------------------------------------------------------- 1 | // (C) COPYRIGHT 2018 TECHNOLUTION BV, GOUDA NL 2 | 3 | // Licensed under the Apache License, Version 2.0 or the MIT license 5 | // , at your 6 | // option. This file may not be copied, modified, or distributed 7 | // except according to those terms. 8 | 9 | extern crate addr2line; 10 | extern crate fallible_iterator; 11 | extern crate gimli; 12 | 13 | use std::string::String; 14 | 15 | use self::fallible_iterator::FallibleIterator; 16 | 17 | use gimli::Abbreviations; 18 | use gimli::AttributeValue::*; 19 | use gimli::CompilationUnitHeader; 20 | use gimli::DebugStr; 21 | use gimli::DebuggingInformationEntry; 22 | use gimli::DwAt; 23 | use gimli::Expression; 24 | use gimli::Reader; 25 | use gimli::ReaderOffset; 26 | 27 | use Context; 28 | 29 | /// Function that returns a string value from an entry's attribute. 30 | /// If no string was found, or an error occured 31 | /// an error message will be returned. 32 | pub fn get_attr_string_value_safe( 33 | entry: &DebuggingInformationEntry, 34 | attr: DwAt, 35 | strings: &DebugStr, 36 | ) -> String { 37 | entry.attr(attr).expect("Error reading attributes") // Note that, if no attribute is present Ok(None) is returned. 38 | .map(|attrib| { 39 | let tmp: Option = attrib.string_value(strings) 40 | .map(|x: R| x.to_string().unwrap_or_else(|_| panic!("Failed to convert {} value to string", attr)).to_string()); 41 | tmp.unwrap_or_else(|| format!("<{} is not a string>", attr).to_string()) 42 | }) 43 | .unwrap_or_else(|| format!("", attr).to_string()) 44 | } 45 | 46 | /// Function that returns the buffer of a string value from an entries attribute. If no string valute was found, `None` will be returned 47 | pub fn get_attr_string_buf( 48 | entry: &DebuggingInformationEntry, 49 | attr: DwAt, 50 | strings: &DebugStr, 51 | ) -> Option { 52 | entry 53 | .attr(attr) 54 | .ok() 55 | .and_then(|att_opt| att_opt.and_then(|att| att.string_value(strings))) 56 | } 57 | 58 | /// Function that returns a string value from an entries attribute. If no string was found, `None` will be returned 59 | pub fn get_attr_string_value( 60 | entry: &DebuggingInformationEntry, 61 | attr: DwAt, 62 | strings: &DebugStr, 63 | ) -> Option { 64 | get_attr_string_buf(entry, attr, strings).map(|buf| buf.to_string().unwrap().to_string()) 65 | } 66 | 67 | /// Returns a byte buffer representing the attribute value. 68 | /// Returns `None` if no such attribute was found, or the value was not `Block`, `Exprloc` or `String`. 69 | #[allow(dead_code)] 70 | pub fn get_attr_buf( 71 | entry: &DebuggingInformationEntry, 72 | attr: DwAt, 73 | ) -> Option { 74 | match entry 75 | .attr(attr) 76 | .map(|att_opt| att_opt.map(|att| att.value())) 77 | { 78 | Ok(Some(value)) => match value { 79 | Block(v) | Exprloc(Expression(v)) | String(v) => Some(v), 80 | _ => None, 81 | }, 82 | _ => None, 83 | } 84 | } 85 | 86 | /// If the attribute has an data value, it will be returned. Else `None` will be given. 87 | pub fn get_attr_u64_value( 88 | entry: &DebuggingInformationEntry, 89 | attr: DwAt, 90 | ) -> Option { 91 | entry 92 | .attr(attr) 93 | .ok() 94 | .and_then(|att_opt| att_opt.and_then(|att| att.udata_value())) 95 | } 96 | 97 | /// If the attribute has an Addr value, it will be returned. Else `None` will be given. 98 | pub fn get_attr_addr_value( 99 | entry: &DebuggingInformationEntry, 100 | attr: DwAt, 101 | ) -> Option { 102 | match entry 103 | .attr(attr) 104 | .map(|att_opt| att_opt.map(|att| att.value())) 105 | { 106 | Ok(Some(Addr(value))) => Some(value), 107 | _ => None, 108 | } 109 | } 110 | 111 | /// Function demangling assembly symbol names. 112 | pub fn demangle_symbol(linkage_name: &str) -> String { 113 | let demangled = addr2line::demangle(linkage_name, gimli::DW_LANG_Rust) 114 | .unwrap_or_else(|| linkage_name.to_string()); 115 | 116 | // Every demangled linkage name contain a hash at the end of the string 117 | // These calls take care to remove that 118 | let last_crate_index = demangled 119 | .rfind("::") 120 | .unwrap_or_else(|| demangled.chars().count()); 121 | demangled[..last_crate_index].to_string() 122 | } 123 | 124 | /// Prints detailed information of an entry (fo debugging purposes) 125 | #[allow(dead_code)] 126 | pub fn print_entry_details( 127 | entry: &DebuggingInformationEntry, 128 | strings: &DebugStr, 129 | ) { 130 | let offset = entry.offset().0.into_u64(); 131 | let name = get_attr_string_value_safe(entry, gimli::DW_AT_name, &strings); 132 | let linkage_name = get_attr_string_value_safe(entry, gimli::DW_AT_linkage_name, &strings); 133 | 134 | println!( 135 | "Entry details: offset: {:x}, name: {}, linkage_name: {}, present attributes:", 136 | offset, name, linkage_name 137 | ); 138 | let _t: Vec<_> = entry 139 | .attrs() 140 | .iterator() 141 | .map(|x| x.unwrap()) 142 | .inspect(|val| { 143 | println!(" - {}", val.name()); 144 | }) 145 | .collect(); 146 | } 147 | 148 | /// Function that determines the Rust version that was used to compile the binary in `ctx`. 149 | pub fn get_rust_version(ctx: &Context) -> Option { 150 | ctx.dwarf_info 151 | .units() 152 | .iterator() 153 | .filter_map(|unit_header| { 154 | let unit_header = unit_header.unwrap(); 155 | let abbrevs = unit_header.abbreviations(&ctx.dwarf_abbrev).unwrap(); 156 | let mut entries = unit_header.entries(&abbrevs); 157 | 158 | let (_, entry) = entries 159 | .next_dfs() 160 | .expect("First compilation unit could not be selected") 161 | .unwrap(); 162 | 163 | let producer = get_attr_string_value(&entry, gimli::DW_AT_producer, &ctx.dwarf_strings); 164 | 165 | // Assume producer is in a format like 'clang LLVM (rustc version 1.26.0 (a77568041 2018-05-07))' 166 | producer.map(|p| { 167 | p.rsplit("rustc version") 168 | .next() 169 | .expect("Unexpected producer string format") 170 | .trim() 171 | .split_whitespace() 172 | .next() 173 | .expect("Unexpected producer string format") 174 | .to_string() 175 | }) 176 | }) 177 | .next() 178 | } 179 | 180 | /// Function getting an attribute string value. If the attribute is not present, but an DW_AT_abstract_origin 181 | /// attribute is present, the references entry will be checked for the desired attribute. 182 | pub fn get_attr_str_with_origin_traversal( 183 | cu: &CompilationUnitHeader, 184 | entry: &DebuggingInformationEntry, 185 | attr: DwAt, 186 | abbrev: &Abbreviations, 187 | strings: &DebugStr, 188 | ) -> String { 189 | match get_attr_string_value(entry, attr, strings) { 190 | Some(name) => name, 191 | _ => match entry.attr_value(gimli::DW_AT_abstract_origin) { 192 | Ok(Some(UnitRef(offset))) => { 193 | let mut origin_cursor = cu.entries_at_offset(abbrev, offset).unwrap(); 194 | let (_, origin) = origin_cursor.next_dfs().unwrap().unwrap(); 195 | get_attr_string_value_safe(origin, attr, strings) 196 | } 197 | oth => panic!("DW_AT_abstract_origin is not UnitRef, but {:x?}", oth), 198 | }, 199 | } 200 | } 201 | 202 | #[cfg(test)] 203 | mod tests { 204 | use super::*; 205 | 206 | /// Test demangling a symbol with a hash 207 | #[test] 208 | pub fn test_demangle_symbol() { 209 | assert_eq!(demangle_symbol(&"sym::hash".to_string()), "sym".to_string()); 210 | } 211 | 212 | /// Test demangling a symbol without a hash 213 | #[test] 214 | pub fn test_demangle_symbol_no_double_dot() { 215 | assert_eq!(demangle_symbol(&"123".to_string()), "123".to_string()); 216 | } 217 | } 218 | -------------------------------------------------------------------------------- /lib/callgraph/src/parse/mod.rs: -------------------------------------------------------------------------------- 1 | // (C) COPYRIGHT 2018 TECHNOLUTION BV, GOUDA NL 2 | 3 | // Licensed under the Apache License, Version 2.0 or the MIT license 5 | // , at your 6 | // option. This file may not be copied, modified, or distributed 7 | // except according to those terms. 8 | 9 | extern crate addr2line; 10 | extern crate capstone; 11 | extern crate elf; 12 | extern crate gimli; 13 | extern crate goblin; 14 | extern crate object; 15 | extern crate std; 16 | 17 | use errors::ResultExt; 18 | use errors::*; 19 | 20 | use CallGraphOptions; 21 | use Context; 22 | 23 | use addr2line::Context as Addr2LineContext; 24 | 25 | use capstone::arch::BuildsCapstone; 26 | use capstone::Capstone; 27 | 28 | use gimli::DebugAbbrev; 29 | use gimli::DebugInfo; 30 | use gimli::DebugLine; 31 | use gimli::DebugStr; 32 | use gimli::EndianBuf; 33 | use gimli::LittleEndian; 34 | 35 | use object::ElfFile; 36 | use object::File; 37 | use object::Object; 38 | 39 | // This import was falsely flagged as unused by the rust compiler. 40 | #[allow(unused_imports)] 41 | use object::ObjectSection; 42 | 43 | /// Trait marking objects that are able to parse a binary into appropriate ELF/DWARF/Disassembled information 44 | pub trait Parser { 45 | fn parse<'a>(&self, file_content: &'a [u8]) -> Result>; 46 | } 47 | 48 | // Implementation of `Parser` that does parsing without any extraordinary processing. 49 | struct DefaultParser; 50 | 51 | /// Wrapper data structure for debug info 52 | type DebugData<'a> = ( 53 | DebugInfo>, 54 | DebugAbbrev>, 55 | DebugStr>, 56 | DebugLine>, 57 | ); 58 | 59 | impl Parser for DefaultParser { 60 | fn parse<'a>(&self, file_content: &'a [u8]) -> Result> { 61 | let elf = ElfFile::parse(&file_content) 62 | .map_err(|message| Error::from(ErrorKind::ParseError(message.to_string())))?; 63 | 64 | let file = File::parse(&file_content) 65 | .map_err(|message| Error::from(ErrorKind::ParseError(message.to_string())))?; 66 | 67 | let file_context = Addr2LineContext::new(&file).map_err(|message| { 68 | Error::from(ErrorKind::ParseError(format!( 69 | "Could not construct addr2line context from file: {}", 70 | message.to_string() 71 | ))) 72 | })?; 73 | 74 | let data_byte = elf.elf() 75 | .header 76 | .endianness() 77 | .chain_err(|| ErrorKind::ParseError("Invalid endianness specifier".to_string()))?; 78 | let mode_byte = elf.elf().header.e_ident[elf::types::EI_CLASS]; 79 | 80 | let endianness = gimli::LittleEndian; 81 | let mode = match mode_byte { 82 | 1 => capstone::arch::x86::ArchMode::Mode32, 83 | 2 => capstone::arch::x86::ArchMode::Mode64, 84 | _ => { 85 | return Err( 86 | ErrorKind::ParseError("ELF file has invalid mode bit".to_string()).into(), 87 | ) 88 | } 89 | }; 90 | 91 | if data_byte == goblin::container::Endian::Big { 92 | return Err( 93 | ErrorKind::NotSupported("Big endian files not supported yet".to_string()).into(), 94 | ); 95 | } 96 | 97 | let (dwarf_info, dwarf_abbrev, dwarf_strings, dwarf_line) = 98 | self.parse_dwarf_info(&elf, endianness)?; 99 | 100 | let mut capstone = Capstone::new() 101 | .x86() 102 | .mode(mode) 103 | .detail(true) 104 | .build() 105 | .expect("Failed to construct disassembler"); 106 | capstone 107 | .set_detail(true) 108 | .expect("Failed to enable detailed mode"); 109 | 110 | Ok(Context { 111 | elf, 112 | file_context, 113 | dwarf_info, 114 | dwarf_abbrev, 115 | dwarf_strings, 116 | dwarf_line, 117 | capstone, 118 | }) 119 | } 120 | } 121 | 122 | impl DefaultParser { 123 | fn parse_dwarf_info<'a>( 124 | &self, 125 | elf: &ElfFile<'a>, 126 | endianness: LittleEndian, 127 | ) -> Result> { 128 | let debug_info_data = elf.section_data_by_name(".debug_info") 129 | .chain_err(|| ErrorKind::ParseError("No .debug_info section in binary".to_string()))?; 130 | let dwarf_info = DebugInfo::new(debug_info_data, endianness); 131 | 132 | let debug_abbrev_data = elf.section_data_by_name(".debug_abbrev") 133 | .chain_err(|| ErrorKind::ParseError("No .debug_abbrev section in binary".to_string()))?; 134 | let dwarf_abbrev = DebugAbbrev::new(debug_abbrev_data, endianness); 135 | 136 | let debug_str_data = elf.section_data_by_name(".debug_str") 137 | .chain_err(|| ErrorKind::ParseError("No .debug_str section in binary".to_string()))?; 138 | let dwarf_strings = DebugStr::new(debug_str_data, endianness); 139 | 140 | let debug_line_data = elf.section_data_by_name(".debug_line") 141 | .chain_err(|| ErrorKind::ParseError("No .debug_line section in binary".to_string()))?; 142 | let dwarf_line = DebugLine::new(debug_line_data, endianness); 143 | 144 | Ok((dwarf_info, dwarf_abbrev, dwarf_strings, dwarf_line)) 145 | } 146 | } 147 | 148 | pub fn get_parser(_cmd_args: &CallGraphOptions) -> Box { 149 | Box::new(DefaultParser) 150 | } 151 | 152 | #[cfg(test)] 153 | mod test { 154 | use super::*; 155 | use gimli::AttributeValue::DebugStrRef; 156 | use gimli::*; 157 | extern crate test_common; 158 | 159 | /// Test if the function panics if the passed byte array is not a valid elf file 160 | #[test] 161 | pub fn test_invalid_file_content() { 162 | assert!(DefaultParser.parse(&[]).is_err()); 163 | } 164 | 165 | /// Test if the `DefaultParser` parses debug abbreviations correctly. 166 | /// We validate this by checking the tag, children and an attribute for 2 DIE's. 167 | #[test] 168 | pub fn test_example_binary_debug_abbrev() { 169 | let file_content = &test_common::load_test_binary_as_bytes( 170 | "threads", 171 | &test_common::TestSubjectType::DebugStableRustc, 172 | ).unwrap(); 173 | 174 | let context = DefaultParser.parse(file_content).unwrap(); 175 | // In order to compare debug sections, we will select a subset of abbreviations, and validate if they match 176 | let abbreviations = context 177 | .dwarf_abbrev 178 | .abbreviations(DebugAbbrevOffset(0)) 179 | .expect("Error parsing abbreviations"); 180 | 181 | let compile_unit_abbrev = abbreviations.get(1).expect("No abbreviation for code 1"); 182 | let compile_unit_attributes = compile_unit_abbrev.attributes(); 183 | let cu_attr_4 = compile_unit_attributes[3]; 184 | 185 | assert_eq!(compile_unit_abbrev.code(), 1); 186 | assert_eq!(compile_unit_abbrev.tag(), DW_TAG_compile_unit); 187 | assert_eq!(compile_unit_abbrev.has_children(), true); 188 | 189 | assert_eq!(compile_unit_attributes.len(), 8); 190 | 191 | assert_eq!(cu_attr_4.name(), DW_AT_stmt_list); 192 | assert_eq!(cu_attr_4.form(), DW_FORM_sec_offset); 193 | 194 | let formal_param_abbrev = abbreviations.get(9).expect("No abbreviation for code 9"); 195 | let formal_param_attributes = formal_param_abbrev.attributes(); 196 | let fp_attr_5 = formal_param_attributes[3]; 197 | 198 | assert_eq!(formal_param_abbrev.code(), 9); 199 | assert_eq!(formal_param_abbrev.tag(), DW_TAG_member); 200 | assert_eq!(formal_param_abbrev.has_children(), false); 201 | 202 | assert_eq!(formal_param_attributes.len(), 4); 203 | 204 | assert_eq!(fp_attr_5.name(), DW_AT_data_member_location); 205 | assert_eq!(fp_attr_5.form(), DW_FORM_data1); 206 | } 207 | 208 | /// Test if the `DefaultParser` parses debug info correctly. 209 | /// We validate this by checking the name attribute of an DIE. 210 | #[test] 211 | pub fn test_example_binary_debug_info() { 212 | let file_content = &test_common::load_test_binary_as_bytes( 213 | "threads", 214 | &test_common::TestSubjectType::DebugStableRustc, 215 | ).unwrap(); 216 | 217 | let context = DefaultParser.parse(file_content).unwrap(); 218 | 219 | // Next, we will inspect some of the debug info entries, and check if they match 220 | let unit_1 = context.dwarf_info.units().next().unwrap().unwrap(); 221 | 222 | assert_eq!(unit_1.header_size(), 11); 223 | 224 | let abbr = context 225 | .dwarf_abbrev 226 | .abbreviations(DebugAbbrevOffset(0)) 227 | .expect("Failed to parse abbreviations"); 228 | let mut cursor = unit_1.entries(&abbr); 229 | cursor.next_dfs().expect("Failed to mode to initial entry"); 230 | 231 | let (_, entry_1) = cursor.next_dfs().unwrap().unwrap(); 232 | 233 | let name_value_ref = match entry_1.attr(DW_AT_name).unwrap().unwrap().value() { 234 | DebugStrRef(offset) => offset, 235 | _ => panic!("No DebugStrRef return type"), 236 | }; 237 | let name_value = context.dwarf_strings.get_str(name_value_ref).unwrap(); 238 | 239 | assert_eq!(entry_1.tag(), DW_TAG_namespace); 240 | assert_eq!(name_value, EndianBuf::new(b"core", LittleEndian)); 241 | } 242 | 243 | /// Test if the `DefaultParser` parses debug info correctly. 244 | /// We validate this by checking the first instruction of the text section. 245 | #[test] 246 | pub fn test_example_binary_disassembly() { 247 | let file_content = &test_common::load_test_binary_as_bytes( 248 | "threads", 249 | &test_common::TestSubjectType::DebugStableRustc, 250 | ).unwrap(); 251 | 252 | let context = DefaultParser.parse(file_content).unwrap(); 253 | // Test 42th instruction 254 | let instr = context 255 | .capstone 256 | .disasm_all(context.elf.section_data_by_name(".text").unwrap(), 0x6210) 257 | .unwrap() 258 | .iter() 259 | .next() 260 | .unwrap(); 261 | 262 | assert_eq!(instr.address(), 0x6210); 263 | assert_eq!(instr.bytes(), &[65 as u8, 87 as u8]); 264 | assert_eq!(instr.mnemonic(), Some("push")); 265 | assert_eq!(instr.op_str(), Some("r15")); 266 | } 267 | } 268 | -------------------------------------------------------------------------------- /lib/panic_analysis/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "panic_analysis" 3 | version = "0.1.0" 4 | authors = [ 5 | "Bart van Schaick ", 6 | "Dominique van Cuilenborg ", 7 | "Fabian Stelmach ", 8 | "Aron Zwaan ", 9 | "Erwin Gribnau " 10 | ] 11 | workspace = "../.." 12 | 13 | [dependencies] 14 | callgraph = { path = "../../lib/callgraph" } 15 | gimli = "0.15.0" 16 | fallible-iterator = "0.1.2" 17 | error-chain = "0.12.0" 18 | 19 | [dev-dependencies] 20 | elf = "0.0.10" 21 | capstone = { git = "https://github.com/capstone-rust/capstone-rs.git", rev = "dc10578aa27414afc93f0a87959b2f0c22dc66c3" } 22 | object = "0.7.0" 23 | addr2line = "0.6.0" 24 | test_common = { path = "../../lib/test_common" } 25 | -------------------------------------------------------------------------------- /lib/panic_analysis/src/binary/mod.rs: -------------------------------------------------------------------------------- 1 | // (C) COPYRIGHT 2018 TECHNOLUTION BV, GOUDA NL 2 | 3 | // Licensed under the Apache License, Version 2.0 or the MIT license 5 | // , at your 6 | // option. This file may not be copied, modified, or distributed 7 | // except according to those terms. 8 | 9 | use callgraph::Binary; 10 | use errors::*; 11 | use std::path::Path; 12 | use AnalysisOptions; 13 | 14 | /// Trait marking objects that are able to make sure there is a binary to analyze 15 | pub trait BinaryBuilder { 16 | fn build(&self) -> Result; 17 | } 18 | 19 | /// Implementation of BinaryBuilder that consumes a pre-existing binary 20 | #[derive(Debug, Clone)] 21 | struct ExistingBinaryBuilder { 22 | path: String, 23 | } 24 | 25 | impl BinaryBuilder for ExistingBinaryBuilder { 26 | fn build(&self) -> Result { 27 | let path = Path::new(&self.path); 28 | if path.exists() { 29 | Ok(Binary { path }) 30 | } else { 31 | Err(ErrorKind::IOError(self.path.to_string()).into()) 32 | } 33 | } 34 | } 35 | 36 | /// Function providing a correct implementation of BinaryBuilder, based on the provided options 37 | pub fn get_builder(options: &AnalysisOptions) -> Result> { 38 | let path = options 39 | .binary_path 40 | .clone() 41 | .ok_or("No path to binary provided.")?; 42 | Ok(Box::new(ExistingBinaryBuilder { path })) 43 | } 44 | 45 | #[cfg(test)] 46 | mod test { 47 | extern crate std; 48 | extern crate test_common; 49 | 50 | use self::test_common::TestSubjectType; 51 | use binary::*; 52 | 53 | /// Test that a file exists at the given binary path 54 | #[test] 55 | fn test_binary_exists() { 56 | let path = test_common::get_test_subject_path("hello_world", &TestSubjectType::Debug); 57 | 58 | let path_string = path.to_str().unwrap().to_string(); 59 | let builder = ExistingBinaryBuilder { path: path_string }; 60 | let b = builder.build().unwrap(); 61 | 62 | assert_eq!(b.path, path); 63 | } 64 | 65 | /// Test that a panic is thrown when the binary file is not found 66 | #[test] 67 | fn test_binary_does_not_exist() { 68 | let mut path = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")); 69 | path.push("res/resource_should_not_exist"); 70 | 71 | let path_string = path.to_str().unwrap().to_string(); 72 | let builder = ExistingBinaryBuilder { path: path_string }; 73 | assert!(builder.build().is_err()); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /lib/panic_analysis/src/filter/mod.rs: -------------------------------------------------------------------------------- 1 | // (C) COPYRIGHT 2018 TECHNOLUTION BV, GOUDA NL 2 | 3 | // Licensed under the Apache License, Version 2.0 or the MIT license 5 | // , at your 6 | // option. This file may not be copied, modified, or distributed 7 | // except according to those terms. 8 | 9 | mod panic_filter; 10 | mod whitelist_filter; 11 | 12 | use AnalysisOptions; 13 | use RustigCallGraph; 14 | 15 | use callgraph::Context; 16 | 17 | use std::fmt::Debug; 18 | 19 | /// Trait used to filter nodes from call graph 20 | pub trait NodeFilter: Debug { 21 | fn filter_nodes(&self, call_graph: &mut RustigCallGraph, context: &Context); 22 | #[cfg(test)] 23 | fn get_type_name(&self) -> &str; 24 | } 25 | 26 | /// Combination of multiple node filters 27 | #[derive(Debug)] 28 | struct CombinedNodeFilter { 29 | filters: Vec>, 30 | } 31 | 32 | impl NodeFilter for CombinedNodeFilter { 33 | fn filter_nodes(&self, call_graph: &mut RustigCallGraph, context: &Context) { 34 | self.filters 35 | .iter() 36 | .for_each(|filter| filter.filter_nodes(call_graph, context)); 37 | } 38 | #[cfg(test)] 39 | fn get_type_name(&self) -> &str { 40 | "CombinedNodeFilter" 41 | } 42 | } 43 | 44 | #[derive(Debug)] 45 | pub struct NullNodeFilter; 46 | 47 | impl NodeFilter for NullNodeFilter { 48 | fn filter_nodes(&self, _call_graph: &mut RustigCallGraph, _context: &Context) {} 49 | #[cfg(test)] 50 | fn get_type_name(&self) -> &str { 51 | "NullNodeFilter" 52 | } 53 | } 54 | 55 | pub fn get_node_filters(options: &AnalysisOptions) -> Box { 56 | let filters: Vec> = vec![ 57 | panic_filter::get_panic_filter(&options), 58 | whitelist_filter::get_whitelist_filter(&options), 59 | ]; 60 | 61 | Box::new(CombinedNodeFilter { filters }) 62 | } 63 | 64 | #[cfg(test)] 65 | mod tests { 66 | extern crate callgraph; 67 | extern crate test_common; 68 | 69 | use super::*; 70 | use callgraph::CallGraph; 71 | use std::cell::Cell; 72 | use std::collections::HashMap; 73 | use std::rc::Rc; 74 | 75 | use test_utils; 76 | 77 | /// Filter used as a mock of NodeFilter 78 | #[derive(Debug)] 79 | struct FakeNodeFilter { 80 | pub filter_nodes_calls: Rc>, 81 | } 82 | 83 | /// Whenever filter_nodes is called, increment the call counter 84 | impl NodeFilter for FakeNodeFilter { 85 | fn filter_nodes(&self, _call_graph: &mut RustigCallGraph, _context: &Context) { 86 | let current = self.filter_nodes_calls.replace(0 as usize); 87 | 88 | self.filter_nodes_calls.replace(current + 1 as usize); 89 | } 90 | #[cfg(test)] 91 | fn get_type_name(&self) -> &str { 92 | "FakeNodeFilter" 93 | } 94 | } 95 | 96 | /// Check if the correct type of panic filter is returned 97 | #[test] 98 | fn correct_panic_filter_type() { 99 | let options = AnalysisOptions { 100 | binary_path: None, 101 | crate_names: Vec::new(), 102 | full_crate_analysis: false, 103 | output_full_callgraph: false, 104 | output_filtered_callgraph: false, 105 | whitelisted_functions: Vec::new(), 106 | }; 107 | 108 | let filter = get_node_filters(&options); 109 | 110 | assert_eq!(filter.get_type_name(), "CombinedNodeFilter"); 111 | } 112 | 113 | /// Check if combined filter calls its children 114 | #[test] 115 | fn combined_filter_works() { 116 | let counter1 = Rc::new(Cell::new(0 as usize)); 117 | let counter2 = Rc::new(Cell::new(0 as usize)); 118 | 119 | let fake_filter1 = Box::new(FakeNodeFilter { 120 | filter_nodes_calls: counter1.clone(), 121 | }); 122 | 123 | let fake_filter2 = Box::new(FakeNodeFilter { 124 | filter_nodes_calls: counter2.clone(), 125 | }); 126 | 127 | let fake_filters: Vec> = vec![fake_filter1, fake_filter2]; 128 | 129 | let combined_filter = CombinedNodeFilter { 130 | filters: fake_filters, 131 | }; 132 | 133 | let graph = callgraph::petgraph::stable_graph::StableGraph::new(); 134 | 135 | let mut cg = CallGraph { 136 | graph, 137 | proc_index: HashMap::new(), 138 | call_index: HashMap::new(), 139 | }; 140 | 141 | let file = test_common::load_test_binary_as_bytes( 142 | "hello_world", 143 | &test_common::TestSubjectType::Debug, 144 | ).unwrap(); 145 | 146 | let context = test_utils::parse_context(&file); 147 | 148 | combined_filter.filter_nodes(&mut cg, &context); 149 | 150 | assert_eq!(counter1.get(), 1); 151 | assert_eq!(counter2.get(), 1); 152 | } 153 | 154 | } 155 | -------------------------------------------------------------------------------- /lib/panic_analysis/src/filter/panic_filter.rs: -------------------------------------------------------------------------------- 1 | // (C) COPYRIGHT 2018 TECHNOLUTION BV, GOUDA NL 2 | 3 | // Licensed under the Apache License, Version 2.0 or the MIT license 5 | // , at your 6 | // option. This file may not be copied, modified, or distributed 7 | // except according to those terms. 8 | 9 | use AnalysisOptions; 10 | use RustigCallGraph; 11 | 12 | use callgraph::Context; 13 | 14 | use filter::NodeFilter; 15 | 16 | #[derive(Debug)] 17 | struct NonPanicFilter; 18 | 19 | impl NodeFilter for NonPanicFilter { 20 | fn filter_nodes(&self, call_graph: &mut RustigCallGraph, _context: &Context) { 21 | call_graph 22 | .graph 23 | .retain_nodes(|graph, node| graph[node].borrow().attributes.is_panic.get()); 24 | } 25 | #[cfg(test)] 26 | fn get_type_name(&self) -> &str { 27 | "NonPanicFilter" 28 | } 29 | } 30 | 31 | pub fn get_panic_filter(_options: &AnalysisOptions) -> Box { 32 | Box::new(NonPanicFilter) 33 | } 34 | 35 | #[cfg(test)] 36 | mod tests { 37 | extern crate callgraph; 38 | extern crate capstone; 39 | extern crate test_common; 40 | 41 | use super::*; 42 | use callgraph::Crate; 43 | use callgraph::Procedure; 44 | 45 | use self::capstone::arch::BuildsCapstone; 46 | use self::capstone::prelude::Capstone; 47 | 48 | use std::cell::Cell; 49 | use std::cell::RefCell; 50 | 51 | use callgraph::CallGraph; 52 | use std::collections::HashMap; 53 | use std::rc::Rc; 54 | use IntermediateBacktrace::NoTrace; 55 | use RDPProcedureMetaData; 56 | use RustigCallGraph; 57 | 58 | use test_utils; 59 | 60 | /// Helper function to create a procedure with a given name and crate name 61 | fn create_procedure_with_name( 62 | name: String, 63 | crate_name: String, 64 | is_panic: bool, 65 | ) -> Procedure { 66 | let capstone = Capstone::new() 67 | .x86() 68 | .mode(capstone::arch::x86::ArchMode::Mode64) 69 | .detail(true) 70 | .build() 71 | .expect("Failed to construct disassembler"); 72 | 73 | let empty_vec = Vec::new(); 74 | 75 | Procedure { 76 | name: name.clone(), 77 | linkage_name: "linkage_name".to_string(), 78 | linkage_name_demangled: format!("{}_demangled", name).to_string(), 79 | defining_crate: Crate { 80 | name: crate_name, 81 | version: Some("3.0.0".to_string()), 82 | }, 83 | start_address: 32, 84 | size: 64, 85 | location: None, 86 | attributes: RDPProcedureMetaData { 87 | analysis_target: Cell::new(false), 88 | entry_point: Cell::new(false), 89 | is_panic: Cell::new(is_panic), 90 | is_panic_origin: Cell::new(false), 91 | intermediate_panic_calls: RefCell::new(NoTrace), 92 | visited: Cell::new(true), 93 | whitelisted: Cell::new(false), 94 | reachable_from_entry_point: Cell::new(true), 95 | }, 96 | disassembly: capstone.disasm_all(&empty_vec, 0x1000).unwrap(), 97 | } 98 | } 99 | 100 | /// Create callgraph with a (possibly panicking) procedure with some specific name 101 | fn create_callgraph(proc_name: &str, with_panic: bool) -> RustigCallGraph { 102 | let procedure_foo = 103 | create_procedure_with_name(proc_name.to_string(), "CrateFoo".to_string(), with_panic); 104 | 105 | let mut og = callgraph::petgraph::stable_graph::StableGraph::new(); 106 | og.add_node(Rc::new(RefCell::new(procedure_foo))); 107 | 108 | let cg: RustigCallGraph = CallGraph { 109 | graph: og, 110 | proc_index: HashMap::new(), 111 | call_index: HashMap::new(), 112 | }; 113 | 114 | cg 115 | } 116 | 117 | /// Check if the correct type of panic filter is returned 118 | #[test] 119 | fn correct_panic_filter_type() { 120 | let options = AnalysisOptions { 121 | binary_path: None, 122 | crate_names: Vec::new(), 123 | full_crate_analysis: false, 124 | output_full_callgraph: false, 125 | output_filtered_callgraph: false, 126 | whitelisted_functions: Vec::new(), 127 | }; 128 | 129 | let filter = get_panic_filter(&options); 130 | 131 | assert_eq!(filter.get_type_name(), "NonPanicFilter"); 132 | } 133 | 134 | /// Test whether non-panicking nodes are filtered away 135 | #[test] 136 | fn panic_filtering() { 137 | let mut cg = create_callgraph("foo", false); 138 | 139 | let file = test_common::load_test_binary_as_bytes( 140 | "hello_world", 141 | &test_common::TestSubjectType::Debug, 142 | ).unwrap(); 143 | let context = test_utils::parse_context(&file); 144 | 145 | assert_eq!(1, cg.graph.node_indices().count()); 146 | 147 | NonPanicFilter.filter_nodes(&mut cg, &context); 148 | 149 | assert_eq!(0, cg.graph.node_indices().count()); 150 | } 151 | 152 | /// Test whether panicking nodes are not filtered away 153 | #[test] 154 | fn non_panic_filtering() { 155 | let mut cg = create_callgraph("foo", true); 156 | 157 | let file = test_common::load_test_binary_as_bytes( 158 | "hello_world", 159 | &test_common::TestSubjectType::Debug, 160 | ).unwrap(); 161 | let context = test_utils::parse_context(&file); 162 | 163 | assert_eq!(1, cg.graph.node_indices().count()); 164 | 165 | NonPanicFilter.filter_nodes(&mut cg, &context); 166 | 167 | assert_eq!(1, cg.graph.node_indices().count()); 168 | } 169 | } 170 | -------------------------------------------------------------------------------- /lib/panic_analysis/src/filter/whitelist_filter.rs: -------------------------------------------------------------------------------- 1 | // (C) COPYRIGHT 2018 TECHNOLUTION BV, GOUDA NL 2 | 3 | // Licensed under the Apache License, Version 2.0 or the MIT license 5 | // , at your 6 | // option. This file may not be copied, modified, or distributed 7 | // except according to those terms. 8 | 9 | use AnalysisOptions; 10 | use RustigCallGraph; 11 | use RustigGraph; 12 | 13 | use callgraph::Context; 14 | 15 | use filter::NodeFilter; 16 | use filter::NullNodeFilter; 17 | 18 | use callgraph::petgraph::prelude::Direction::Outgoing; 19 | use callgraph::petgraph::stable_graph::NodeIndex; 20 | use callgraph::petgraph::visit::EdgeRef; 21 | 22 | #[derive(Debug)] 23 | struct WhiteListFunctionFilter; 24 | 25 | impl WhiteListFunctionFilter { 26 | fn traverse_graph(&self, index: NodeIndex, graph: &RustigGraph) { 27 | let procedure = graph[index].borrow(); 28 | 29 | // If function is whitelisted, ignore it 30 | if procedure.attributes.whitelisted.get() { 31 | return; 32 | } 33 | 34 | procedure.attributes.reachable_from_entry_point.set(true); 35 | 36 | // Check whether this node has any neighbors, if not return 37 | graph 38 | .edges_directed(index, Outgoing) 39 | .filter(|edge| !edge.weight().borrow().attributes.whitelisted.get()) 40 | .map(|edge| edge.target()) 41 | .for_each(|neighbor_idx| { 42 | let neighbor = graph[neighbor_idx].borrow(); 43 | 44 | // Check whether neighbor has been visited yet, if not visit it before we continue (DFS) 45 | if !neighbor.attributes.reachable_from_entry_point.get() { 46 | self.traverse_graph(neighbor_idx, graph); 47 | } 48 | }); 49 | } 50 | } 51 | 52 | impl NodeFilter for WhiteListFunctionFilter { 53 | fn filter_nodes(&self, call_graph: &mut RustigCallGraph, _context: &Context) { 54 | // Mark all nodes that are reachable from main 55 | { 56 | let node_indices = call_graph.graph.node_indices(); 57 | node_indices 58 | .filter(|idx| call_graph.graph[*idx].borrow().attributes.entry_point.get()) 59 | .for_each(|entry_point_idx| { 60 | self.traverse_graph(entry_point_idx, &call_graph.graph) 61 | }); 62 | } 63 | 64 | call_graph.graph.retain_nodes(|grp, node_idx| { 65 | let node = grp[node_idx].borrow(); 66 | !node.attributes.analysis_target.get() 67 | || node.attributes.reachable_from_entry_point.get() 68 | }); 69 | 70 | call_graph.graph.retain_edges(|grp, edge_idx| { 71 | let edge = grp[edge_idx].borrow(); 72 | !edge.attributes.whitelisted.get() 73 | }); 74 | } 75 | #[cfg(test)] 76 | fn get_type_name(&self) -> &str { 77 | "WhiteListFunctionFilter" 78 | } 79 | } 80 | 81 | pub fn get_whitelist_filter(options: &AnalysisOptions) -> Box { 82 | if options.full_crate_analysis { 83 | Box::new(NullNodeFilter) 84 | } else { 85 | Box::new(WhiteListFunctionFilter) 86 | } 87 | } 88 | 89 | #[cfg(test)] 90 | mod tests { 91 | extern crate callgraph; 92 | extern crate capstone; 93 | extern crate test_common; 94 | 95 | use super::*; 96 | use callgraph::Crate; 97 | use callgraph::Procedure; 98 | 99 | use self::capstone::arch::BuildsCapstone; 100 | use self::capstone::prelude::Capstone; 101 | 102 | use std::cell::Cell; 103 | use std::cell::RefCell; 104 | 105 | use callgraph::Invocation; 106 | use callgraph::InvocationType; 107 | use std::collections::HashMap; 108 | use std::rc::Rc; 109 | use IntermediateBacktrace::NoTrace; 110 | use RDPInvocationMetaData; 111 | use RDPProcedureMetaData; 112 | 113 | use test_utils; 114 | 115 | /// Helper function to create a procedure with a given name and crate name 116 | fn create_procedure_with_name( 117 | name: String, 118 | crate_name: String, 119 | is_entry: bool, 120 | whitelisted: bool, 121 | ) -> Procedure { 122 | let capstone = Capstone::new() 123 | .x86() 124 | .mode(capstone::arch::x86::ArchMode::Mode64) 125 | .detail(true) 126 | .build() 127 | .expect("Failed to construct disassembler"); 128 | 129 | let empty_vec = Vec::new(); 130 | 131 | Procedure { 132 | name: name.clone(), 133 | linkage_name: "linkage_name".to_string(), 134 | linkage_name_demangled: format!("{}_demangled", name).to_string(), 135 | defining_crate: Crate { 136 | name: crate_name, 137 | version: Some("3.0.0".to_string()), 138 | }, 139 | start_address: 32, 140 | size: 64, 141 | location: None, 142 | attributes: RDPProcedureMetaData { 143 | analysis_target: Cell::new(true), 144 | entry_point: Cell::new(is_entry), 145 | is_panic: Cell::new(true), 146 | is_panic_origin: Cell::new(false), 147 | intermediate_panic_calls: RefCell::new(NoTrace), 148 | visited: Cell::new(false), 149 | whitelisted: Cell::new(whitelisted), 150 | reachable_from_entry_point: Cell::new(false), 151 | }, 152 | disassembly: capstone.disasm_all(&empty_vec, 0x1000).unwrap(), 153 | } 154 | } 155 | 156 | fn create_callgraph( 157 | reachable_from_main: bool, 158 | whitelist_bar: bool, 159 | whitelist_edge: bool, 160 | ) -> RustigCallGraph { 161 | let procedure_foo = 162 | create_procedure_with_name("foo".to_string(), "CrateFoo".to_string(), true, false); 163 | 164 | let procedure_bar = create_procedure_with_name( 165 | "bar".to_string(), 166 | "CrateFoo".to_string(), 167 | false, 168 | whitelist_bar, 169 | ); 170 | 171 | let mut og = callgraph::petgraph::stable_graph::StableGraph::new(); 172 | let i_foo = og.add_node(Rc::new(RefCell::new(procedure_foo))); 173 | let i_bar = og.add_node(Rc::new(RefCell::new(procedure_bar))); 174 | 175 | let invocation = Rc::new(RefCell::new(Invocation { 176 | instruction_address: 0x144562, 177 | invocation_type: InvocationType::Direct, 178 | frames: Vec::new(), 179 | attributes: RDPInvocationMetaData { 180 | whitelisted: Cell::new(whitelist_edge), 181 | }, 182 | })); 183 | 184 | if reachable_from_main { 185 | og.add_edge(i_foo, i_bar, invocation); 186 | } 187 | 188 | let cg = RustigCallGraph { 189 | graph: og, 190 | proc_index: HashMap::new(), 191 | call_index: HashMap::new(), 192 | }; 193 | 194 | cg 195 | } 196 | 197 | /// Check if the correct type of panic filter is returned 198 | #[test] 199 | fn correct_whitelist_filter_type() { 200 | let options = AnalysisOptions { 201 | binary_path: None, 202 | crate_names: Vec::new(), 203 | full_crate_analysis: false, 204 | output_full_callgraph: false, 205 | output_filtered_callgraph: false, 206 | whitelisted_functions: Vec::new(), 207 | }; 208 | 209 | let filter = get_whitelist_filter(&options); 210 | 211 | assert_eq!(filter.get_type_name(), "WhiteListFunctionFilter"); 212 | } 213 | 214 | /// Check if the correct type of panic filter is returned 215 | #[test] 216 | fn correct_whitelist_filter_full_crate_type() { 217 | let options = AnalysisOptions { 218 | binary_path: None, 219 | crate_names: Vec::new(), 220 | full_crate_analysis: true, 221 | output_full_callgraph: false, 222 | output_filtered_callgraph: false, 223 | whitelisted_functions: Vec::new(), 224 | }; 225 | 226 | let filter = get_whitelist_filter(&options); 227 | 228 | assert_eq!(filter.get_type_name(), "NullNodeFilter"); 229 | } 230 | 231 | macro_rules! whitelist_test { 232 | ($($name:ident: $reachable:expr, $whitelist:expr, $whitelist_edge:expr => $node_count:expr,)*) => { 233 | $( 234 | #[test] 235 | fn $name() { 236 | let options = AnalysisOptions { 237 | binary_path: None, 238 | crate_names: Vec::new(), 239 | full_crate_analysis: false, 240 | output_full_callgraph: false, 241 | output_filtered_callgraph: false, 242 | whitelisted_functions: Vec::new(), 243 | }; 244 | 245 | let filter = get_whitelist_filter(&options); 246 | 247 | let mut cg = create_callgraph($reachable, $whitelist, $whitelist_edge); 248 | let file = test_common::load_test_binary_as_bytes( 249 | "hello_world", 250 | &test_common::TestSubjectType::Debug, 251 | ).unwrap(); 252 | let context = test_utils::parse_context(&file); 253 | 254 | assert_eq!(cg.graph.node_indices().count(), 2); 255 | 256 | filter.filter_nodes(&mut cg, &context); 257 | 258 | assert_eq!(cg.graph.node_indices().count(), $node_count); 259 | } 260 | )* 261 | } 262 | } 263 | 264 | /// The whitelist test have four inputs which decide how a call graph for the test is built. 265 | /// The graph always has two nodes - `foo` and `bar`. 266 | /// First input decides whether the two nodes of the callgraph should be connected 267 | /// Second input decides whether the `bar` node should be filtered 268 | /// Third input decides whether the `foo-bar` edge should be filtered 269 | /// Fourth input is the expected value of the amount of nodes after filtering, either 1 or 2 270 | whitelist_test!{ 271 | // Filter reachable edge and node 272 | filter_both_reachable: true, true, true => 1, 273 | 274 | // Filter reachable node 275 | whitelist_edge_reachable: true, true, false => 1, 276 | 277 | // Filter reachable edge 278 | whitelist_node_reachable: true, false, true => 1, 279 | 280 | // Don't filter unreachable nodes 281 | no_whitelist_filtering: true, false, false => 2, 282 | 283 | // NOTE: All unreachable nodes are filtered and so all the tests below get filtered. 284 | // Filter unreachable node and edge 285 | whitelist_both_unreachable: false, true, true => 1, 286 | // Filter unreachable edge 287 | whitelist_edge_unreachable: false, true, false => 1, 288 | // Filter unreachable node 289 | whitelist_node_unreachable: false, false, true => 1, 290 | // Don't filter unreachable node or edge 291 | unreachable_no_whitelist_filtering: false, false, false => 1, 292 | } 293 | } 294 | -------------------------------------------------------------------------------- /lib/panic_analysis/src/graph_output/mod.rs: -------------------------------------------------------------------------------- 1 | // (C) COPYRIGHT 2018 TECHNOLUTION BV, GOUDA NL 2 | 3 | // Licensed under the Apache License, Version 2.0 or the MIT license 5 | // , at your 6 | // option. This file may not be copied, modified, or distributed 7 | // except according to those terms. 8 | 9 | use AnalysisOptions; 10 | 11 | use RustigCallGraph; 12 | 13 | use std::fmt::Debug; 14 | 15 | use std::fs::File; 16 | use std::io::BufWriter; 17 | use std::io::Write; 18 | use std::path::Path; 19 | 20 | /// Trait representing some output format of the graph 21 | pub trait GraphOutput: Debug { 22 | fn write_graph(&self, call_graph: &RustigCallGraph); 23 | #[cfg(test)] 24 | fn get_type_name(&self) -> &str; 25 | } 26 | 27 | /// Struct that represents a graph output that dumps a .dot file 28 | #[derive(Debug)] 29 | struct DotGraphOutput { 30 | stage: String, 31 | filename: String, 32 | } 33 | 34 | impl GraphOutput for DotGraphOutput { 35 | fn write_graph(&self, call_graph: &RustigCallGraph) { 36 | let dot = call_graph.dot(); 37 | let filename = format!("rdp-callgraph-{}-{}.dot", self.filename, self.stage); 38 | let write_file = File::create(&filename) 39 | .unwrap_or_else(|_| panic!("The file {} could not be created", filename)); 40 | let mut writer = BufWriter::new(&write_file); 41 | 42 | let dot_out = format!("{:?}", dot); 43 | let dot_out = dot_out.replace("\\\\", "\\"); 44 | 45 | let success = write!(&mut writer, "{}", dot_out); 46 | if success.is_err() { 47 | panic!("Could not write dot file"); 48 | } 49 | } 50 | #[cfg(test)] 51 | fn get_type_name(&self) -> &str { 52 | "DotGraphOutput" 53 | } 54 | } 55 | 56 | /// Struct that represents graph output that does nothing 57 | #[derive(Debug)] 58 | struct NoGraphOutput; 59 | 60 | impl GraphOutput for NoGraphOutput { 61 | fn write_graph(&self, _call_graph: &RustigCallGraph) {} 62 | #[cfg(test)] 63 | fn get_type_name(&self) -> &str { 64 | "NoGraphOutput" 65 | } 66 | } 67 | 68 | fn get_boxed_output(active: bool, stage: String, filename: String) -> Box { 69 | if active { 70 | return Box::new(DotGraphOutput { stage, filename }); 71 | } 72 | Box::new(NoGraphOutput) 73 | } 74 | 75 | fn get_name_from_options(options: &AnalysisOptions) -> String { 76 | match options.binary_path { 77 | Some(ref path) => Path::new(&path) 78 | .file_stem() 79 | .expect("Could not extract file stem") 80 | .to_os_string() 81 | .into_string() 82 | .expect("Could not convert binary file stem to string"), 83 | None => "unknown-filename".to_string(), 84 | } 85 | } 86 | 87 | /// Graph output for filtered graph 88 | pub fn get_graph_output_filtered(options: &AnalysisOptions) -> Box { 89 | get_boxed_output( 90 | options.output_filtered_callgraph, 91 | "filtered".to_string(), 92 | get_name_from_options(&options), 93 | ) 94 | } 95 | 96 | /// Graph output for unfiltered, full graph 97 | pub fn get_graph_output_full(options: &AnalysisOptions) -> Box { 98 | get_boxed_output( 99 | options.output_full_callgraph, 100 | "full".to_string(), 101 | get_name_from_options(&options), 102 | ) 103 | } 104 | 105 | #[cfg(test)] 106 | mod test { 107 | extern crate test_common; 108 | 109 | use self::test_common::*; 110 | use super::*; 111 | 112 | use AnalysisOptions; 113 | 114 | fn create_options(path: Option, full: bool, filtered: bool) -> AnalysisOptions { 115 | AnalysisOptions { 116 | binary_path: path, 117 | crate_names: vec![], 118 | full_crate_analysis: true, 119 | output_full_callgraph: full, 120 | output_filtered_callgraph: filtered, 121 | whitelisted_functions: vec![], 122 | } 123 | } 124 | 125 | /// Test whether the graph_output type is correct for the situation where `full` graph is made 126 | #[test] 127 | fn test_proper_graph_output_type_full() { 128 | let possibilities = vec![ 129 | (false, false, "NoGraphOutput"), 130 | (true, false, "DotGraphOutput"), 131 | (false, true, "NoGraphOutput"), 132 | (true, true, "DotGraphOutput"), 133 | ]; 134 | 135 | possibilities.iter().for_each(|poss| { 136 | let output = get_graph_output_full(&create_options(None, poss.0, poss.1)); 137 | assert_eq!( 138 | poss.2, 139 | output.get_type_name(), 140 | "The combination {:?} produced output {}, while {} was expected", 141 | poss, 142 | output.get_type_name(), 143 | poss.2 144 | ); 145 | }) 146 | } 147 | 148 | /// Test whether the graph_output type is correct for the situation where `filtered` graph is made 149 | #[test] 150 | fn test_proper_graph_output_type_filtered() { 151 | let possibilities = vec![ 152 | (false, false, "NoGraphOutput"), 153 | (true, false, "NoGraphOutput"), 154 | (false, true, "DotGraphOutput"), 155 | (true, true, "DotGraphOutput"), 156 | ]; 157 | 158 | possibilities.iter().for_each(|poss| { 159 | let output = get_graph_output_filtered(&create_options(None, poss.0, poss.1)); 160 | assert_eq!( 161 | poss.2, 162 | output.get_type_name(), 163 | "The combination {:?} produced output {}, while {} was expected", 164 | poss, 165 | output.get_type_name(), 166 | poss.2 167 | ); 168 | }) 169 | } 170 | 171 | /// Check whether the correct file stem is generated from a test subject 172 | /// File stem is the name of the file, without its extension 173 | /// Stem of `/home/rdp.dot` would be `rdp` 174 | #[test] 175 | fn test_file_stem() { 176 | let path = get_test_subject_path("hello_world", &TestSubjectType::Debug) 177 | .to_str() 178 | .unwrap() 179 | .to_string(); 180 | let options = create_options(Some(path), false, false); 181 | assert_eq!(get_name_from_options(&options), "hello_world") 182 | } 183 | } 184 | -------------------------------------------------------------------------------- /lib/panic_analysis/src/marker/entry_point.rs: -------------------------------------------------------------------------------- 1 | // (C) COPYRIGHT 2018 TECHNOLUTION BV, GOUDA NL 2 | 3 | // Licensed under the Apache License, Version 2.0 or the MIT license 5 | // , at your 6 | // option. This file may not be copied, modified, or distributed 7 | // except according to those terms. 8 | 9 | extern crate callgraph; 10 | extern crate fallible_iterator; 11 | extern crate gimli; 12 | 13 | use AnalysisOptions; 14 | use RustigCallGraph; 15 | 16 | use callgraph::Context; 17 | 18 | use self::fallible_iterator::FallibleIterator; 19 | 20 | use marker::CodeMarker; 21 | 22 | use callgraph::dwarf_utils; 23 | 24 | use gimli::CompilationUnitHeader; 25 | use gimli::EndianBuf; 26 | use gimli::LittleEndian; 27 | 28 | /// Implementation of the `CodeMarker` to mark the main entry procedure 29 | #[derive(Debug)] 30 | struct MainEntryCodeMarker; 31 | 32 | impl CodeMarker for MainEntryCodeMarker { 33 | fn mark_code(&self, call_graph: &RustigCallGraph, context: &Context) { 34 | context 35 | .dwarf_info 36 | .units() 37 | .iterator() 38 | .map(Result::unwrap) 39 | .for_each(|unit| { 40 | // Find entries in compilation unit 41 | self.mark_entry_point(call_graph, &context, unit) 42 | }) 43 | } 44 | #[cfg(test)] 45 | fn get_type_name(&self) -> &str { 46 | "MainEntryCodeMarker" 47 | } 48 | } 49 | 50 | impl MainEntryCodeMarker { 51 | fn mark_entry_point( 52 | &self, 53 | call_graph: &RustigCallGraph, 54 | context: &Context, 55 | unit: CompilationUnitHeader>, 56 | ) -> () { 57 | let abbrevs = unit.abbreviations(&context.dwarf_abbrev).unwrap(); 58 | let mut entries = unit.entries(&abbrevs); 59 | // Iterate over the entries 60 | while let Some((_, entry)) = entries.next_dfs().unwrap() { 61 | // If we find an entry for a function that has DW_AT_main_subprogram set ot true 62 | // We mark the corresponding procedure as entry point. 63 | if entry.tag() == gimli::DW_TAG_subprogram { 64 | if let Some(gimli::AttributeValue::Flag(true)) = 65 | entry.attr_value(gimli::DW_AT_main_subprogram).unwrap() 66 | { 67 | let start_address = 68 | dwarf_utils::get_attr_addr_value(&entry, gimli::DW_AT_low_pc) 69 | .expect("No DW_AT_low_pc attribute found for function"); 70 | 71 | let node_index = call_graph.proc_index[&start_address]; 72 | call_graph.graph[node_index] 73 | .borrow() 74 | .attributes 75 | .entry_point 76 | .replace(true); 77 | } 78 | } 79 | } 80 | } 81 | } 82 | 83 | pub fn get_entry_points_marker(_options: &AnalysisOptions) -> Box { 84 | Box::new(MainEntryCodeMarker) 85 | } 86 | 87 | #[cfg(test)] 88 | mod test { 89 | extern crate capstone; 90 | extern crate gimli; 91 | extern crate object; 92 | extern crate std; 93 | extern crate test_common; 94 | 95 | use self::capstone::arch::BuildsCapstone; 96 | use self::capstone::Capstone; 97 | 98 | use super::*; 99 | 100 | use callgraph::Crate; 101 | use callgraph::Procedure; 102 | 103 | use RDPProcedureMetaData; 104 | 105 | use std::cell::Cell; 106 | use std::cell::RefCell; 107 | use std::collections::HashMap; 108 | use std::rc::Rc; 109 | 110 | use IntermediateBacktrace::NoTrace; 111 | 112 | use test_utils; 113 | 114 | /// Helper function to create a procedure with a given name, crate name and address 115 | fn create_procedure_with_name( 116 | name: String, 117 | crate_name: String, 118 | starting_address: u64, 119 | ) -> Procedure { 120 | let capstone = Capstone::new() 121 | .x86() 122 | .mode(capstone::arch::x86::ArchMode::Mode64) 123 | .detail(true) 124 | .build() 125 | .expect("Failed to construct disassembler"); 126 | 127 | let empty_vec = Vec::new(); 128 | 129 | Procedure { 130 | name: name.clone(), 131 | linkage_name: "linkage_name".to_string(), 132 | linkage_name_demangled: format!("{}_demangled", name).to_string(), 133 | defining_crate: Crate { 134 | name: crate_name, 135 | version: Some("3.0.0".to_string()), 136 | }, 137 | start_address: starting_address, 138 | size: 64, 139 | location: None, 140 | attributes: RDPProcedureMetaData { 141 | analysis_target: Cell::new(false), 142 | entry_point: Cell::new(false), 143 | is_panic: Cell::new(false), 144 | is_panic_origin: Cell::new(false), 145 | intermediate_panic_calls: RefCell::new(NoTrace), 146 | visited: Cell::new(false), 147 | whitelisted: Cell::new(false), 148 | reachable_from_entry_point: Cell::new(true), 149 | }, 150 | disassembly: capstone.disasm_all(&empty_vec, 0x1000).unwrap(), 151 | } 152 | } 153 | 154 | // Given some specific context, find the address of main 155 | fn find_main_address(context: &Context) -> u64 { 156 | let mut iter = context.dwarf_info.units(); 157 | let mut start_address: u64 = 0x0; 158 | while let Some(unit) = iter.next().unwrap() { 159 | // Parse the abbreviations for this compilation unit. 160 | let abbrevs = unit.abbreviations(&context.dwarf_abbrev).unwrap(); 161 | 162 | // Iterate over all of this compilation unit's entries. 163 | let mut entries = unit.entries(&abbrevs); 164 | while let Some((_, entry)) = entries.next_dfs().unwrap() { 165 | // If we find an entry for a function, print it. 166 | if let Some(gimli::AttributeValue::Flag(true)) = 167 | entry.attr_value(gimli::DW_AT_main_subprogram).unwrap() 168 | { 169 | start_address = dwarf_utils::get_attr_addr_value(&entry, gimli::DW_AT_low_pc) 170 | .expect("No DW_AT_low_pc attribute found for function"); 171 | } 172 | } 173 | } 174 | 175 | start_address 176 | } 177 | 178 | /// Test to ensure attributes are marked correctly as entry points 179 | #[test] 180 | fn test_marks_correctly() { 181 | let file_content = &test_common::load_test_binary_as_bytes( 182 | "hello_world", 183 | &test_common::TestSubjectType::Debug, 184 | ).unwrap(); 185 | let context = test_utils::parse_context(file_content); 186 | 187 | let main_addr = find_main_address(&context); 188 | let motmain_addr = 0x126; 189 | 190 | let procedure_main = 191 | create_procedure_with_name("main".to_string(), "mycrate".to_string(), main_addr); 192 | let procedure_not_main = 193 | create_procedure_with_name("notmain".to_string(), "mycrate".to_string(), motmain_addr); 194 | 195 | let mut og = callgraph::petgraph::stable_graph::StableGraph::new(); 196 | let node_index_main = og.add_node(Rc::new(RefCell::new(procedure_main))); 197 | let node_index_notmain = og.add_node(Rc::new(RefCell::new(procedure_not_main))); 198 | 199 | let mut proc_index = HashMap::new(); 200 | proc_index.insert(main_addr, node_index_main); 201 | proc_index.insert(motmain_addr, node_index_notmain); 202 | 203 | let call_graph = RustigCallGraph { 204 | graph: og, 205 | proc_index, 206 | call_index: HashMap::new(), 207 | }; 208 | 209 | let marker = MainEntryCodeMarker; 210 | marker.mark_code(&call_graph, &context); 211 | 212 | let value_main = &call_graph.graph[node_index_main] 213 | .borrow() 214 | .attributes 215 | .entry_point; 216 | let value_notmain = &call_graph.graph[node_index_notmain] 217 | .borrow() 218 | .attributes 219 | .entry_point; 220 | 221 | assert!(value_main.get()); 222 | assert!(!value_notmain.get()); 223 | } 224 | } 225 | -------------------------------------------------------------------------------- /lib/panic_analysis/src/marker/mod.rs: -------------------------------------------------------------------------------- 1 | // (C) COPYRIGHT 2018 TECHNOLUTION BV, GOUDA NL 2 | 3 | // Licensed under the Apache License, Version 2.0 or the MIT license 5 | // , at your 6 | // option. This file may not be copied, modified, or distributed 7 | // except according to those terms. 8 | 9 | mod analysis_target; 10 | mod entry_point; 11 | mod function_whitelist; 12 | mod panic; 13 | 14 | use AnalysisOptions; 15 | use RustigCallGraph; 16 | 17 | use callgraph::Context; 18 | 19 | use std::fmt::Debug; 20 | 21 | use marker::analysis_target::get_panic_analysis_target_marker; 22 | use marker::entry_point::get_entry_points_marker; 23 | use marker::panic::get_panic_marker; 24 | 25 | pub trait CodeMarker: Debug { 26 | fn mark_code(&self, call_graph: &RustigCallGraph, context: &Context); 27 | #[cfg(test)] 28 | fn get_type_name(&self) -> &str; 29 | } 30 | 31 | /// Combination of multiple `CodeMarker` implementations. 32 | #[derive(Debug)] 33 | struct CombinedCodeMarker { 34 | markers: Vec>, 35 | } 36 | 37 | impl CodeMarker for CombinedCodeMarker { 38 | fn mark_code(&self, call_graph: &RustigCallGraph, context: &Context) { 39 | self.markers 40 | .iter() 41 | .for_each(|marker| marker.mark_code(call_graph, context)); 42 | } 43 | #[cfg(test)] 44 | fn get_type_name(&self) -> &str { 45 | "CombinedCodeMarker" 46 | } 47 | } 48 | 49 | /// Implementation of the `CodeMarker` to not mark anything 50 | #[derive(Debug)] 51 | // Since `EntryPointAnalysisTargetMarker` is implemented, this struct is not used anymore 52 | // We might still keep it here for future reference 53 | #[allow(dead_code)] 54 | struct NullMarker; 55 | 56 | impl CodeMarker for NullMarker { 57 | fn mark_code(&self, _call_graph: &RustigCallGraph, _context: &Context) { 58 | return; 59 | } 60 | #[cfg(test)] 61 | fn get_type_name(&self) -> &str { 62 | "NullMarker" 63 | } 64 | } 65 | 66 | pub fn get_code_markers(options: &AnalysisOptions) -> Box { 67 | let panic_analysis_target_marker = get_panic_analysis_target_marker(options); 68 | let main_code_marker = get_entry_points_marker(options); 69 | let panic_marker = get_panic_marker(options); 70 | 71 | let mut markers = vec![main_code_marker, panic_marker, panic_analysis_target_marker]; 72 | 73 | if !options.whitelisted_functions.is_empty() { 74 | let whitelists = options.whitelisted_functions.to_vec(); 75 | 76 | markers.insert( 77 | 0, 78 | Box::new(function_whitelist::FunctionWhitelistMarker { whitelists }), 79 | ) 80 | } 81 | 82 | Box::new(CombinedCodeMarker { markers }) 83 | } 84 | 85 | #[cfg(test)] 86 | mod test { 87 | extern crate callgraph; 88 | extern crate capstone; 89 | extern crate gimli; 90 | extern crate object; 91 | extern crate std; 92 | extern crate test_common; 93 | 94 | use super::*; 95 | 96 | use std::cell::Cell; 97 | use std::collections::HashMap; 98 | 99 | use std::rc::Rc; 100 | 101 | use test_utils; 102 | 103 | #[derive(Debug)] 104 | struct FakeCodeMarker { 105 | called: Rc>, 106 | } 107 | 108 | impl CodeMarker for FakeCodeMarker { 109 | fn mark_code(&self, _call_graph: &RustigCallGraph, _context: &Context) { 110 | self.called.replace(true); 111 | } 112 | #[cfg(test)] 113 | fn get_type_name(&self) -> &str { 114 | "FakeCodeMarker" 115 | } 116 | } 117 | 118 | /// Verify that `CombinedCodeMarker` calls all it's children when .`mark_code` is executed 119 | #[test] 120 | fn test_markers_calls() { 121 | let og = callgraph::petgraph::stable_graph::StableGraph::new(); 122 | 123 | let call_graph = RustigCallGraph { 124 | graph: og, 125 | proc_index: HashMap::new(), 126 | call_index: HashMap::new(), 127 | }; 128 | 129 | let cell1 = Rc::new(Cell::new(false)); 130 | let cell1_rc = cell1.clone(); 131 | let marker1 = Box::new(FakeCodeMarker { called: cell1 }); 132 | 133 | let cell2 = Rc::new(Cell::new(false)); 134 | let cell2_rc = cell2.clone(); 135 | let marker2 = Box::new(FakeCodeMarker { called: cell2 }); 136 | 137 | let markers: Vec> = vec![marker1, marker2]; 138 | let all_markers = CombinedCodeMarker { markers }; 139 | 140 | let file_content = &test_common::load_test_binary_as_bytes( 141 | "hello_world", 142 | &test_common::TestSubjectType::Debug, 143 | ).unwrap(); 144 | let context = test_utils::parse_context(file_content); 145 | 146 | all_markers.mark_code(&call_graph, &context); 147 | 148 | assert!(cell1_rc.get()); 149 | assert!(cell2_rc.get()); 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /lib/panic_analysis/src/patterns/message_panic.rs: -------------------------------------------------------------------------------- 1 | // (C) COPYRIGHT 2018 TECHNOLUTION BV, GOUDA NL 2 | 3 | // Licensed under the Apache License, Version 2.0 or the MIT license 5 | // , at your 6 | // option. This file may not be copied, modified, or distributed 7 | // except according to those terms. 8 | 9 | use callgraph::Context; 10 | use patterns::PatternFinder; 11 | use std::collections::HashMap; 12 | use AnalysisOptions; 13 | use PanicCallsCollection; 14 | use PanicPattern; 15 | use PanicPattern::Arithmetic; 16 | 17 | /// Implementation of the `PatternFinder` to categorize panic traces based on messages 18 | struct MessagePatternFinder<'a> { 19 | // A hashmap that maps a message to the pattern the trace should be recognized as. 20 | message_pattern_mapping: HashMap<&'a str, PanicPattern>, 21 | } 22 | 23 | impl<'a> PatternFinder for MessagePatternFinder<'a> { 24 | fn find_patterns(&self, _ctx: &Context, panic_calls: &PanicCallsCollection) { 25 | panic_calls 26 | .calls 27 | .iter() 28 | .filter(|panic_trace| panic_trace.message.is_some()) 29 | .for_each(|panic_trace| { 30 | self.message_pattern_mapping 31 | .get(panic_trace.message.as_ref().unwrap().as_str()) 32 | .map(|pattern| panic_trace.pattern.replace(*pattern)); 33 | }) 34 | } 35 | } 36 | 37 | pub fn get_messages_pattern_finder(_options: &AnalysisOptions) -> Box { 38 | let mut messages_map = HashMap::new(); 39 | messages_map.insert("attempt to add with overflow", Arithmetic); 40 | messages_map.insert("attempt to subtract with overflow", Arithmetic); 41 | messages_map.insert("attempt to multiply with overflow", Arithmetic); 42 | 43 | messages_map.insert("attempt to divide with overflow", Arithmetic); 44 | messages_map.insert( 45 | "attempt to calculate the remainder with overflow", 46 | Arithmetic, 47 | ); 48 | messages_map.insert("attempt to divide by zero", Arithmetic); 49 | messages_map.insert( 50 | "attempt to calculate the remainder with a divisor of zero", 51 | Arithmetic, 52 | ); 53 | 54 | messages_map.insert("attempt to shift left with overflow", Arithmetic); 55 | messages_map.insert("attempt to shift right with overflow", Arithmetic); 56 | 57 | Box::new(MessagePatternFinder { 58 | message_pattern_mapping: messages_map, 59 | }) 60 | } 61 | 62 | #[cfg(test)] 63 | mod test { 64 | extern crate test_common; 65 | 66 | use self::test_common::*; 67 | use super::*; 68 | 69 | use PanicCall; 70 | use PanicPattern::Indexing; 71 | use PanicPattern::Unrecognized; 72 | 73 | use std::cell::RefCell; 74 | 75 | use test_utils::*; 76 | 77 | /// Test if a `PanicCall` is marked correctly if its message is in `message_pattern_mapping`. 78 | #[test] 79 | pub fn test_recognized_correctly_first_entry_first_index() { 80 | let file_content = 81 | load_test_binary_as_bytes("hello_world", &TestSubjectType::Debug).unwrap(); 82 | let context = parse_context(&file_content); 83 | 84 | let mut message_pattern_mapping = HashMap::new(); 85 | message_pattern_mapping.insert("arith", Arithmetic); 86 | message_pattern_mapping.insert("index", Indexing); 87 | 88 | let finder = MessagePatternFinder { 89 | message_pattern_mapping, 90 | }; 91 | 92 | let panic_collection = PanicCallsCollection { 93 | calls: vec![PanicCall { 94 | backtrace: Vec::new(), 95 | pattern: RefCell::new(Unrecognized), 96 | contains_dynamic_invocation: false, 97 | message: Some("arith".to_string()), 98 | }], 99 | }; 100 | 101 | finder.find_patterns(&context, &panic_collection); 102 | 103 | assert_eq!(*panic_collection.calls[0].pattern.borrow(), Arithmetic); 104 | } 105 | 106 | /// Test if a `PanicCall` is marked correctly if its message is in `message_pattern_mapping`. 107 | #[test] 108 | pub fn test_recognized_correctly_first_entry_last_index() { 109 | let file_content = 110 | load_test_binary_as_bytes("hello_world", &TestSubjectType::Debug).unwrap(); 111 | let context = parse_context(&file_content); 112 | 113 | let mut message_pattern_mapping = HashMap::new(); 114 | message_pattern_mapping.insert("arith", Arithmetic); 115 | message_pattern_mapping.insert("index", Indexing); 116 | 117 | let finder = MessagePatternFinder { 118 | message_pattern_mapping, 119 | }; 120 | 121 | let panic_collection = PanicCallsCollection { 122 | calls: vec![PanicCall { 123 | backtrace: Vec::new(), 124 | pattern: RefCell::new(Unrecognized), 125 | contains_dynamic_invocation: false, 126 | message: Some("index".to_string()), 127 | }], 128 | }; 129 | 130 | finder.find_patterns(&context, &panic_collection); 131 | 132 | assert_eq!(*panic_collection.calls[0].pattern.borrow(), Indexing); 133 | } 134 | 135 | /// Test if multiple `PanicCall`s are marked correctly if their messages are in `message_pattern_mapping`. 136 | #[test] 137 | pub fn test_recognized_correctly_multiple() { 138 | let file_content = 139 | load_test_binary_as_bytes("hello_world", &TestSubjectType::Debug).unwrap(); 140 | let context = parse_context(&file_content); 141 | 142 | let mut message_pattern_mapping = HashMap::new(); 143 | message_pattern_mapping.insert("arith", Arithmetic); 144 | message_pattern_mapping.insert("index", Indexing); 145 | 146 | let finder = MessagePatternFinder { 147 | message_pattern_mapping, 148 | }; 149 | 150 | let panic_collection = PanicCallsCollection { 151 | calls: vec![ 152 | PanicCall { 153 | backtrace: Vec::new(), 154 | pattern: RefCell::new(Unrecognized), 155 | contains_dynamic_invocation: false, 156 | message: Some("index".to_string()), 157 | }, 158 | PanicCall { 159 | backtrace: Vec::new(), 160 | pattern: RefCell::new(Unrecognized), 161 | contains_dynamic_invocation: false, 162 | message: Some("arith".to_string()), 163 | }, 164 | ], 165 | }; 166 | 167 | finder.find_patterns(&context, &panic_collection); 168 | 169 | assert_eq!(*panic_collection.calls[0].pattern.borrow(), Indexing); 170 | assert_eq!(*panic_collection.calls[1].pattern.borrow(), Arithmetic); 171 | } 172 | 173 | /// Test if a `PanicCall`s is marked correctly if it is further in the trace. 174 | #[test] 175 | pub fn test_recognized_correctly_last_entry_last_index() { 176 | let file_content = 177 | load_test_binary_as_bytes("hello_world", &TestSubjectType::Debug).unwrap(); 178 | let context = parse_context(&file_content); 179 | 180 | let mut message_pattern_mapping = HashMap::new(); 181 | message_pattern_mapping.insert("arith", Arithmetic); 182 | message_pattern_mapping.insert("index", Indexing); 183 | 184 | let finder = MessagePatternFinder { 185 | message_pattern_mapping, 186 | }; 187 | 188 | let panic_collection = PanicCallsCollection { 189 | calls: vec![ 190 | PanicCall { 191 | backtrace: Vec::new(), 192 | pattern: RefCell::new(Unrecognized), 193 | contains_dynamic_invocation: false, 194 | message: None, 195 | }, 196 | PanicCall { 197 | backtrace: Vec::new(), 198 | pattern: RefCell::new(Unrecognized), 199 | contains_dynamic_invocation: false, 200 | message: None, 201 | }, 202 | PanicCall { 203 | backtrace: Vec::new(), 204 | pattern: RefCell::new(Unrecognized), 205 | contains_dynamic_invocation: false, 206 | message: None, 207 | }, 208 | PanicCall { 209 | backtrace: Vec::new(), 210 | pattern: RefCell::new(Unrecognized), 211 | contains_dynamic_invocation: false, 212 | message: Some("index".to_string()), 213 | }, 214 | ], 215 | }; 216 | 217 | finder.find_patterns(&context, &panic_collection); 218 | 219 | assert_eq!(*panic_collection.calls[3].pattern.borrow(), Indexing); 220 | } 221 | 222 | /// Test if a `PanicCall` is not marked when its message is not in `message_pattern_mapping`. 223 | #[test] 224 | pub fn test_not_recognized_no_matching_message() { 225 | let file_content = 226 | load_test_binary_as_bytes("hello_world", &TestSubjectType::Debug).unwrap(); 227 | let context = parse_context(&file_content); 228 | 229 | let mut message_pattern_mapping = HashMap::new(); 230 | message_pattern_mapping.insert("arith", Arithmetic); 231 | message_pattern_mapping.insert("index", Indexing); 232 | 233 | let finder = MessagePatternFinder { 234 | message_pattern_mapping, 235 | }; 236 | 237 | let panic_collection = PanicCallsCollection { 238 | calls: vec![PanicCall { 239 | backtrace: Vec::new(), 240 | pattern: RefCell::new(Unrecognized), 241 | contains_dynamic_invocation: false, 242 | message: Some("not present".to_string()), 243 | }], 244 | }; 245 | 246 | finder.find_patterns(&context, &panic_collection); 247 | 248 | assert_eq!(*panic_collection.calls[0].pattern.borrow(), Unrecognized); 249 | } 250 | 251 | /// Test if a `PanicCall` is not marked when its message is `None`. 252 | #[test] 253 | pub fn test_not_recognized_message_none() { 254 | let file_content = 255 | load_test_binary_as_bytes("hello_world", &TestSubjectType::Debug).unwrap(); 256 | let context = parse_context(&file_content); 257 | 258 | let mut message_pattern_mapping = HashMap::new(); 259 | message_pattern_mapping.insert("arith", Arithmetic); 260 | message_pattern_mapping.insert("index", Indexing); 261 | 262 | let finder = MessagePatternFinder { 263 | message_pattern_mapping, 264 | }; 265 | 266 | let panic_collection = PanicCallsCollection { 267 | calls: vec![PanicCall { 268 | backtrace: Vec::new(), 269 | pattern: RefCell::new(Unrecognized), 270 | contains_dynamic_invocation: false, 271 | message: None, 272 | }], 273 | }; 274 | 275 | finder.find_patterns(&context, &panic_collection); 276 | 277 | assert_eq!(*panic_collection.calls[0].pattern.borrow(), Unrecognized); 278 | } 279 | } 280 | -------------------------------------------------------------------------------- /lib/panic_analysis/src/patterns/mod.rs: -------------------------------------------------------------------------------- 1 | // (C) COPYRIGHT 2018 TECHNOLUTION BV, GOUDA NL 2 | 3 | // Licensed under the Apache License, Version 2.0 or the MIT license 5 | // , at your 6 | // option. This file may not be copied, modified, or distributed 7 | // except according to those terms. 8 | 9 | mod direct_panic; 10 | mod function_panic; 11 | mod message_panic; 12 | 13 | use callgraph::Context; 14 | use AnalysisOptions; 15 | use PanicCallsCollection; 16 | 17 | use patterns::direct_panic::get_direct_panic_pattern_finder; 18 | use patterns::function_panic::get_function_names_pattern_finder; 19 | use patterns::message_panic::get_messages_pattern_finder; 20 | 21 | /// Trait marking structs that can recognize common patterns in a panic call 22 | pub trait PatternFinder { 23 | fn find_patterns(&self, ctx: &Context, panic_calls: &PanicCallsCollection); 24 | } 25 | 26 | /// Combination of multiple `PatternFinder` implementations. 27 | struct CombinedPatternFinder { 28 | finders: Vec>, 29 | } 30 | 31 | impl PatternFinder for CombinedPatternFinder { 32 | fn find_patterns(&self, ctx: &Context, panic_calls: &PanicCallsCollection) { 33 | self.finders 34 | .iter() 35 | .for_each(|finder| finder.find_patterns(ctx, panic_calls)) 36 | } 37 | } 38 | 39 | pub fn get_pattern_finder(options: &AnalysisOptions) -> Box { 40 | let direct_panic_finder = get_direct_panic_pattern_finder(options); 41 | let unwrap_panic_finder = get_function_names_pattern_finder(options); 42 | let message_panic_finder = get_messages_pattern_finder(options); 43 | 44 | Box::new(CombinedPatternFinder { 45 | finders: vec![ 46 | direct_panic_finder, 47 | unwrap_panic_finder, 48 | message_panic_finder, 49 | ], 50 | }) 51 | } 52 | 53 | #[cfg(test)] 54 | mod test { 55 | extern crate callgraph; 56 | extern crate capstone; 57 | extern crate gimli; 58 | extern crate object; 59 | extern crate std; 60 | extern crate test_common; 61 | 62 | use super::*; 63 | 64 | use std::cell::Cell; 65 | use std::rc::Rc; 66 | 67 | use test_utils; 68 | 69 | struct FakePatternFinder { 70 | called: Rc>, 71 | } 72 | 73 | impl PatternFinder for FakePatternFinder { 74 | fn find_patterns(&self, _context: &Context, _panic_calls: &PanicCallsCollection) { 75 | self.called.replace(true); 76 | } 77 | } 78 | 79 | /// Verify that `CombinedPatternFinder` calls all it's children when .`find_patterns` is executed 80 | #[test] 81 | fn test_markers_calls() { 82 | let cell1 = Rc::new(Cell::new(false)); 83 | let cell1_rc = cell1.clone(); 84 | let marker1 = Box::new(FakePatternFinder { called: cell1 }); 85 | 86 | let cell2 = Rc::new(Cell::new(false)); 87 | let cell2_rc = cell2.clone(); 88 | let marker2 = Box::new(FakePatternFinder { called: cell2 }); 89 | 90 | let finders: Vec> = vec![marker1, marker2]; 91 | let all_finders = CombinedPatternFinder { finders }; 92 | 93 | let file_content = &test_common::load_test_binary_as_bytes( 94 | "hello_world", 95 | &test_common::TestSubjectType::Debug, 96 | ).unwrap(); 97 | let context = test_utils::parse_context(file_content); 98 | 99 | let collection = PanicCallsCollection { calls: Vec::new() }; 100 | all_finders.find_patterns(&context, &collection); 101 | 102 | assert!(cell1_rc.get()); 103 | assert!(cell2_rc.get()); 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /lib/panic_analysis/src/test_utils.rs: -------------------------------------------------------------------------------- 1 | // (C) COPYRIGHT 2018 TECHNOLUTION BV, GOUDA NL 2 | 3 | // Licensed under the Apache License, Version 2.0 or the MIT license 5 | // , at your 6 | // option. This file may not be copied, modified, or distributed 7 | // except according to those terms. 8 | 9 | use callgraph; 10 | use callgraph::addr2line::Context as Addr2LineContext; 11 | use callgraph::capstone::arch::BuildsCapstone; 12 | use callgraph::capstone::Capstone; 13 | use callgraph::gimli::{DebugAbbrev, DebugInfo, DebugLine, DebugStr, EndianBuf, LittleEndian}; 14 | use callgraph::object::{ElfFile, File as ObjectFile, Object}; 15 | use callgraph::Context; 16 | 17 | /// Parse the contents of a file into a callgraph::Context 18 | pub fn parse_context(file_content: &[u8]) -> Context { 19 | let elf = ElfFile::parse(&file_content).expect("Failed to parse file content"); 20 | let file = 21 | ObjectFile::parse(&file_content).expect("Failed to parse file content to File format"); 22 | let file_context = Addr2LineContext::new(&file).expect("Could not construct context from file"); 23 | 24 | let mode = callgraph::capstone::arch::x86::ArchMode::Mode64; 25 | 26 | let (dwarf_info, dwarf_abbrev, dwarf_strings, dwarf_line) = parse_debug_info(&elf); 27 | 28 | let mut capstone = Capstone::new() 29 | .x86() 30 | .mode(mode) 31 | .detail(true) 32 | .build() 33 | .expect("Failed to construct disassembler"); 34 | capstone 35 | .set_detail(true) 36 | .expect("Failed to enable detailed mode"); 37 | 38 | Context { 39 | elf, 40 | file_context, 41 | dwarf_info, 42 | dwarf_abbrev, 43 | dwarf_strings, 44 | dwarf_line, 45 | capstone, 46 | } 47 | } 48 | 49 | /// Extracts various different types of DWARF debugging information. 50 | fn parse_debug_info<'a>( 51 | elf: &ElfFile<'a>, 52 | ) -> ( 53 | DebugInfo>, 54 | DebugAbbrev>, 55 | DebugStr>, 56 | DebugLine>, 57 | ) { 58 | let debug_info_data = elf.section_data_by_name(".debug_info") 59 | .expect("No .debug_info section in binary"); 60 | let dwarf_info = DebugInfo::new(debug_info_data, LittleEndian); 61 | 62 | let debug_abbrev_data = elf.section_data_by_name(".debug_abbrev") 63 | .expect("No .debug_abbrev section in binary"); 64 | let dwarf_abbrev = DebugAbbrev::new(debug_abbrev_data, LittleEndian); 65 | 66 | let debug_str_data = elf.section_data_by_name(".debug_str") 67 | .expect("No .debug_str section in binary"); 68 | let dwarf_strings = DebugStr::new(debug_str_data, LittleEndian); 69 | 70 | let debug_line_data = elf.section_data_by_name(".debug_line") 71 | .expect("No .debug_line section in binary"); 72 | let dwarf_line = DebugLine::new(debug_line_data, LittleEndian); 73 | 74 | (dwarf_info, dwarf_abbrev, dwarf_strings, dwarf_line) 75 | } 76 | -------------------------------------------------------------------------------- /lib/panic_analysis/tests/libcalls.rs: -------------------------------------------------------------------------------- 1 | // (C) COPYRIGHT 2018 TECHNOLUTION BV, GOUDA NL 2 | 3 | // Licensed under the Apache License, Version 2.0 or the MIT license 5 | // , at your 6 | // option. This file may not be copied, modified, or distributed 7 | // except according to those terms. 8 | 9 | #[cfg(test)] 10 | mod test { 11 | extern crate panic_analysis; 12 | extern crate test_common; 13 | 14 | use self::panic_analysis::AnalysisOptions; 15 | use self::test_common::TestSubjectType; 16 | 17 | static ANALYZED_TEST_SUBJECT: &str = "lib_calls"; 18 | 19 | /// Run panic_analysis on `crate_name` and check if there is a `procedure_name` in it 20 | fn backtrace_has_procedure_with_name( 21 | subject_type: &TestSubjectType, 22 | crate_name: &str, 23 | procedure_name: &str, 24 | ) -> bool { 25 | backtrace_has_procedure_with_name_with_count(subject_type, crate_name, procedure_name, 1) 26 | } 27 | 28 | /// Run panic_analysis on `crate_name` and check if there is `count` amount of `procedure_name` in it 29 | fn backtrace_has_procedure_with_name_with_count( 30 | subject_type: &TestSubjectType, 31 | crate_name: &str, 32 | procedure_name: &str, 33 | count: usize, 34 | ) -> bool { 35 | let name = crate_name; 36 | let options = create_options( 37 | test_common::get_test_subject_path(name, subject_type) 38 | .to_str() 39 | .unwrap() 40 | .to_string(), 41 | ); 42 | let panics = panic_analysis::find_panics(&options).unwrap(); 43 | 44 | let panic_count = panics 45 | .calls 46 | .iter() 47 | .filter(|call| { 48 | call.backtrace 49 | .iter() 50 | .any(|node| node.procedure.borrow().name == procedure_name) 51 | }) 52 | .count(); 53 | 54 | panic_count == count 55 | } 56 | 57 | fn create_options(subject: String) -> AnalysisOptions { 58 | AnalysisOptions { 59 | binary_path: Some(subject), 60 | crate_names: vec!["test_subjects".to_string()], 61 | output_full_callgraph: false, 62 | output_filtered_callgraph: false, 63 | whitelisted_functions: vec![], 64 | full_crate_analysis: false, 65 | } 66 | } 67 | 68 | /// Run panic_analysis on `crate_name` and check if there are `procedure_names` in it 69 | fn assert_backtrace_has_procedure_with_name( 70 | subject_type: &TestSubjectType, 71 | crate_name: &str, 72 | procedure_names: &[&str], 73 | ) { 74 | procedure_names.iter().for_each(|procedure_name| { 75 | assert!( 76 | backtrace_has_procedure_with_name(subject_type, crate_name, procedure_name), 77 | "procedure with name: {} not found in panic traces of test_subject {}", 78 | procedure_name, 79 | crate_name 80 | ); 81 | }) 82 | } 83 | 84 | /// The macro lib_test allows for easy definition of tests that check whether a certain panic trace is present in some crate. 85 | /// Example usage: to test whether a test subject `t` contains a call to `s1` and `s2` in release mode, use the following: 86 | /// lib_tests! { t_contains_s: (TestSubjectType::Release, "t", ["s1", "s2"]) 87 | macro_rules! lib_tests { 88 | ($($name:ident: $value:expr,)*) => { 89 | $( 90 | #[test] 91 | fn $name() { 92 | let (subject_type, procedure_names) = $value; 93 | assert_backtrace_has_procedure_with_name(&subject_type, ANALYZED_TEST_SUBJECT, &procedure_names); 94 | } 95 | )* 96 | } 97 | } 98 | 99 | /// Debug mode tests 100 | lib_tests! { 101 | test_lib_calls_box: (TestSubjectType::Debug, ["call_panic_box"]), 102 | test_lib_calls_impl_traits: (TestSubjectType::Debug, ["call_impl_trait_panic"]), 103 | test_lib_calls_lambda_struct: (TestSubjectType::Debug, ["lambda_call", "lambda_struct_as_trait_call"]), 104 | // Note that integer overflow can be detected in debug, but not in release 105 | test_lib_calls_panic_types: (TestSubjectType::Debug, ["standard_panic", "integer_overflow", "index_out_of_bounds","unwrap_none"]), 106 | test_lib_calls_reference_calls: (TestSubjectType::Debug, ["unsized_call_trait", "panic_lambda", "panic_lambda_local"]), 107 | test_lib_calls_structs: (TestSubjectType::Debug, ["struct_call", "struct_call_with_self"]), 108 | test_lib_calls_trait_dynamic: (TestSubjectType::Debug, ["trait_call_dup2", "trait_call_dup_with_self2", "duplicate_trait_call2"]), 109 | test_lib_calls_traits_similar_names: (TestSubjectType::Debug, ["call_impl_trait_panic"]), 110 | test_lib_calls_traits_simple: (TestSubjectType::Debug, ["simple_call", "simple_call2", "simple_call3", "simple_call4"]), 111 | // THIS IS A FALSE POSITIVE! 112 | test_same_vtable: (TestSubjectType::Debug, ["maybe_panic2"]), 113 | } 114 | 115 | /// Release mode tests 116 | lib_tests! { 117 | test_lib_calls_box_release: (TestSubjectType::Release, ["call_panic_box"]), 118 | test_lib_calls_impl_traits_release: (TestSubjectType::Release, ["call_impl_trait_panic"]), 119 | test_lib_calls_lambda_struct_release: (TestSubjectType::Release, ["lambda_call", "lambda_struct_as_trait_call"]), 120 | // Note that integer overflow can be detected in debug, but not in release 121 | test_lib_calls_panic_types_release: (TestSubjectType::Release, ["standard_panic", "index_out_of_bounds","unwrap_none"]), 122 | test_lib_calls_reference_calls_release: (TestSubjectType::Release, ["unsized_call_trait", "panic_lambda", "panic_lambda_local"]), 123 | test_lib_calls_structs_release: (TestSubjectType::Release, ["struct_call", "struct_call_with_self"]), 124 | test_lib_calls_trait_dynamic_release: (TestSubjectType::Release, ["trait_call_dup2", "trait_call_dup_with_self2", "duplicate_trait_call2"]), 125 | test_lib_calls_traits_similar_names_release: (TestSubjectType::Release, ["call_impl_trait_panic"]), 126 | test_lib_calls_traits_simple_release: (TestSubjectType::Release, ["simple_call", "simple_call2", "simple_call3", "simple_call4"]), 127 | // THIS IS A FALSE POSITIVE! 128 | test_same_vtable_release: (TestSubjectType::Release, ["maybe_panic2"]), 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /lib/panic_analysis/tests/panic_message.rs: -------------------------------------------------------------------------------- 1 | // (C) COPYRIGHT 2018 TECHNOLUTION BV, GOUDA NL 2 | 3 | // Licensed under the Apache License, Version 2.0 or the MIT license 5 | // , at your 6 | // option. This file may not be copied, modified, or distributed 7 | // except according to those terms. 8 | 9 | #[cfg(test)] 10 | mod test { 11 | extern crate panic_analysis; 12 | extern crate test_common; 13 | 14 | use self::panic_analysis::*; 15 | use self::test_common::*; 16 | 17 | /// Test if we can retrieve a message on a call to `core::panicking::panic` in debug builds. 18 | /// Since the `panic!` macro in code without `#![no_std]` will use `std::panicking::begin_panic`, we use 'unwrap' here. 19 | #[test] 20 | pub fn test_find_core_message() { 21 | let path = test_common::get_test_subject_path("unwrap", &TestSubjectType::Debug); 22 | let binary_path = path.to_str().map(|x| x.to_string()); 23 | 24 | let options = AnalysisOptions { 25 | binary_path, 26 | crate_names: vec!["test_subjects".to_string()], 27 | full_crate_analysis: false, 28 | output_full_callgraph: false, 29 | output_filtered_callgraph: false, 30 | whitelisted_functions: vec![], 31 | }; 32 | 33 | let calls = find_panics(&options).unwrap(); 34 | let unwrap_call = calls 35 | .calls 36 | .iter() 37 | .find(|trace| { 38 | trace.backtrace[0].procedure.borrow().linkage_name_demangled 39 | == "unwrap::call_unwrap" 40 | }) 41 | .expect("No trace starting at unwrap::call_unwrap found"); 42 | 43 | assert_eq!( 44 | unwrap_call.message, 45 | Some("called `Option::unwrap()` on a `None` value".to_string()) 46 | ); 47 | assert_eq!( 48 | unwrap_call 49 | .backtrace 50 | .last() 51 | .unwrap() 52 | .procedure 53 | .borrow() 54 | .linkage_name_demangled, 55 | "core::panicking::panic" 56 | ); 57 | } 58 | 59 | /// Test if we can retrieve a message on a call to `core::panicking::panic` in release builds. 60 | /// Since the `panic!` macro in code without `#![no_std]` will use `std::panicking::begin_panic`, we use 'unwrap' here. 61 | #[test] 62 | pub fn test_find_core_message_release() { 63 | let path = test_common::get_test_subject_path("unwrap", &TestSubjectType::Release); 64 | let binary_path = path.to_str().map(|x| x.to_string()); 65 | 66 | let options = AnalysisOptions { 67 | binary_path, 68 | crate_names: vec!["test_subjects".to_string()], 69 | full_crate_analysis: false, 70 | output_full_callgraph: false, 71 | output_filtered_callgraph: false, 72 | whitelisted_functions: vec![], 73 | }; 74 | 75 | let calls = find_panics(&options).unwrap(); 76 | let unwrap_call = calls 77 | .calls 78 | .iter() 79 | .find(|trace| { 80 | trace.backtrace[0].procedure.borrow().linkage_name_demangled 81 | == "unwrap::call_unwrap" 82 | }) 83 | .expect("No trace starting at unwrap::call_unwrap found"); 84 | 85 | assert_eq!( 86 | unwrap_call.message, 87 | Some("called `Option::unwrap()` on a `None` value".to_string()) 88 | ); 89 | assert_eq!( 90 | unwrap_call 91 | .backtrace 92 | .last() 93 | .unwrap() 94 | .procedure 95 | .borrow() 96 | .linkage_name_demangled, 97 | "core::panicking::panic" 98 | ); 99 | } 100 | 101 | /// Test if we can retrieve a message on a call to `std::panicking::begin_panic` in debug builds. 102 | #[test] 103 | pub fn test_find_std_message() { 104 | let path = test_common::get_test_subject_path("direct", &TestSubjectType::Debug); 105 | let binary_path = path.to_str().map(|x| x.to_string()); 106 | 107 | let options = AnalysisOptions { 108 | binary_path, 109 | crate_names: vec!["test_subjects".to_string()], 110 | full_crate_analysis: false, 111 | output_full_callgraph: false, 112 | output_filtered_callgraph: false, 113 | whitelisted_functions: vec![], 114 | }; 115 | 116 | let calls = find_panics(&options).unwrap(); 117 | assert_eq!( 118 | calls.calls.len(), 119 | 1, 120 | "Expected 1 trace from 'direct' test subject" 121 | ); 122 | 123 | let direct_call = &calls.calls[0]; 124 | 125 | assert_eq!(direct_call.message, Some("Panic from bar".to_string())); 126 | assert_eq!( 127 | direct_call 128 | .backtrace 129 | .last() 130 | .unwrap() 131 | .procedure 132 | .borrow() 133 | .linkage_name_demangled, 134 | "std::panicking::begin_panic" 135 | ); 136 | } 137 | 138 | /// Test if we can retrieve a message on a call to `std::panicking::begin_panic` in release builds. 139 | #[test] 140 | pub fn test_find_std_message_release() { 141 | let path = test_common::get_test_subject_path("direct", &TestSubjectType::Release); 142 | let binary_path = path.to_str().map(|x| x.to_string()); 143 | 144 | let options = AnalysisOptions { 145 | binary_path, 146 | crate_names: vec!["test_subjects".to_string()], 147 | full_crate_analysis: false, 148 | output_full_callgraph: false, 149 | output_filtered_callgraph: false, 150 | whitelisted_functions: vec![], 151 | }; 152 | 153 | let calls = find_panics(&options).unwrap(); 154 | assert_eq!( 155 | calls.calls.len(), 156 | 1, 157 | "Expected 1 trace from 'direct' test subject" 158 | ); 159 | 160 | let direct_call = &calls.calls[0]; 161 | 162 | assert_eq!(direct_call.message, Some("Panic from bar".to_string())); 163 | assert_eq!( 164 | direct_call 165 | .backtrace 166 | .last() 167 | .unwrap() 168 | .procedure 169 | .borrow() 170 | .linkage_name_demangled, 171 | "std::panicking::begin_panic" 172 | ); 173 | } 174 | 175 | /// Test if we can retrieve a message on a call to `Option::expect` in release builds. 176 | /// Unfortunately, due to complex assembly code, we can not retrieve them in debug. 177 | #[test] 178 | pub fn test_find_option_expect_message_release() { 179 | let path = test_common::get_test_subject_path("unwrap", &TestSubjectType::Release); 180 | let binary_path = path.to_str().map(|x| x.to_string()); 181 | 182 | let options = AnalysisOptions { 183 | binary_path, 184 | crate_names: vec!["test_subjects".to_string()], 185 | full_crate_analysis: false, 186 | output_full_callgraph: false, 187 | output_filtered_callgraph: false, 188 | whitelisted_functions: vec![], 189 | }; 190 | 191 | let calls = find_panics(&options).unwrap(); 192 | let expect_call = calls 193 | .calls 194 | .iter() 195 | .find(|trace| { 196 | trace.backtrace[0].procedure.borrow().linkage_name_demangled 197 | == "unwrap::call_option_expect" 198 | }) 199 | .expect("No trace starting at unwrap::call_option_expect found"); 200 | 201 | assert_eq!( 202 | expect_call.message, 203 | Some("Custom error message for expect call on an option".to_string()) 204 | ); 205 | assert!( 206 | expect_call.backtrace.len() > 3, 207 | "Backtrace too short to originate from expect call" 208 | ); 209 | assert_eq!( 210 | &expect_call.backtrace[2] 211 | .procedure 212 | .borrow() 213 | .linkage_name_demangled, 214 | ">::expect" 215 | ); 216 | } 217 | 218 | /// Test if we can retrieve a message on a call to `Result::expect` in release builds. 219 | /// Unfortunately, due to complex assembly code, we can not retrieve them in debug. 220 | #[test] 221 | pub fn test_find_result_expect_message_release() { 222 | let path = test_common::get_test_subject_path("unwrap", &TestSubjectType::Release); 223 | let binary_path = path.to_str().map(|x| x.to_string()); 224 | 225 | let options = AnalysisOptions { 226 | binary_path, 227 | crate_names: vec!["test_subjects".to_string()], 228 | full_crate_analysis: false, 229 | output_full_callgraph: false, 230 | output_filtered_callgraph: false, 231 | whitelisted_functions: vec![], 232 | }; 233 | 234 | let calls = find_panics(&options).unwrap(); 235 | let expect_call = calls 236 | .calls 237 | .iter() 238 | .find(|trace| { 239 | trace.backtrace[0].procedure.borrow().linkage_name_demangled 240 | == "unwrap::call_expect" 241 | }) 242 | .expect("No trace starting at unwrap::call_expect found"); 243 | 244 | assert_eq!(expect_call.message, Some("No value given".to_string())); 245 | assert!( 246 | expect_call.backtrace.len() > 3, 247 | "Backtrace too short to originate from expect call" 248 | ); 249 | assert_eq!( 250 | &expect_call.backtrace[2] 251 | .procedure 252 | .borrow() 253 | .linkage_name_demangled, 254 | ">::expect" 255 | ); 256 | } 257 | 258 | } 259 | -------------------------------------------------------------------------------- /lib/panic_analysis/tests/recognize-arithmetic.rs: -------------------------------------------------------------------------------- 1 | // (C) COPYRIGHT 2018 TECHNOLUTION BV, GOUDA NL 2 | 3 | // Licensed under the Apache License, Version 2.0 or the MIT license 5 | // , at your 6 | // option. This file may not be copied, modified, or distributed 7 | // except according to those terms. 8 | 9 | #[cfg(test)] 10 | mod test { 11 | extern crate panic_analysis; 12 | extern crate test_common; 13 | 14 | use self::panic_analysis::PanicPattern::Arithmetic; 15 | use self::panic_analysis::*; 16 | use self::test_common::TestSubjectType::*; 17 | use self::test_common::*; 18 | 19 | /// helper method that checks if a trace through function `function_name`, exists in the test subject "arithmetic" on type `subject_type`. 20 | /// It is asserted that the trace has `expected_message` as message as well. 21 | fn assert_trace_present( 22 | function_name: &str, 23 | expected_message: &str, 24 | subject_type: &TestSubjectType, 25 | ) { 26 | let path = test_common::get_test_subject_path("arithmetic", subject_type); 27 | let binary_path = path.to_str().map(|x| x.to_string()); 28 | let options = AnalysisOptions { 29 | binary_path, 30 | crate_names: vec!["test_subjects".to_string()], 31 | full_crate_analysis: false, 32 | output_full_callgraph: false, 33 | output_filtered_callgraph: false, 34 | whitelisted_functions: vec![], 35 | }; 36 | 37 | let calls = find_panics(&options).unwrap(); 38 | 39 | let target_trace = calls 40 | .calls 41 | .iter() 42 | .find(|trace| { 43 | trace.backtrace[1].procedure.borrow().linkage_name_demangled == function_name 44 | }) 45 | .unwrap_or_else(|| panic!("No trace through function '{}' found", function_name)); 46 | 47 | assert_eq!(*target_trace.pattern.borrow(), Arithmetic); 48 | assert_eq!(target_trace.message, Some(expected_message.to_string())); 49 | } 50 | 51 | // Test if an overflow check on a `+` operator is detected with the correct message. 52 | #[test] 53 | fn test_add() { 54 | assert_trace_present( 55 | "test_subjects_lib::arithmetic::add", 56 | "attempt to add with overflow", 57 | &Debug, 58 | ); 59 | } 60 | 61 | // Test if an overflow check on a `-` operator is detected with the correct message. 62 | #[test] 63 | fn test_subtract() { 64 | assert_trace_present( 65 | "test_subjects_lib::arithmetic::subtract", 66 | "attempt to subtract with overflow", 67 | &Debug, 68 | ); 69 | } 70 | 71 | // Test if an overflow check on a `*` operator is detected with the correct message. 72 | #[test] 73 | fn test_multiply() { 74 | assert_trace_present( 75 | "test_subjects_lib::arithmetic::multiply", 76 | "attempt to multiply with overflow", 77 | &Debug, 78 | ); 79 | } 80 | 81 | // Test if an overflow check on a `<<` operator is detected with the correct message. 82 | #[test] 83 | fn test_shift_left() { 84 | assert_trace_present( 85 | "test_subjects_lib::arithmetic::shl", 86 | "attempt to shift left with overflow", 87 | &Debug, 88 | ); 89 | } 90 | 91 | // Test if an overflow check on a `>>` operator is detected with the correct message. 92 | #[test] 93 | fn test_shift_right() { 94 | assert_trace_present( 95 | "test_subjects_lib::arithmetic::shr", 96 | "attempt to shift right with overflow", 97 | &Debug, 98 | ); 99 | } 100 | 101 | // Test if an overflow check on a `/` operator is detected with the correct message. 102 | #[test] 103 | fn test_divide_overflow() { 104 | assert_trace_present( 105 | "test_subjects_lib::arithmetic::divide", 106 | "attempt to divide with overflow", 107 | &Release, 108 | ); 109 | } 110 | 111 | // Test if an overflow check on a `%` operator is detected with the correct message. 112 | #[test] 113 | fn test_remainder_overflow() { 114 | assert_trace_present( 115 | "test_subjects_lib::arithmetic::remainder", 116 | "attempt to calculate the remainder with overflow", 117 | &Release, 118 | ); 119 | } 120 | 121 | // Test if an divide by 0 check on a `/` operator is detected with the correct message. 122 | // This does only occur if the overflow check is optimized away 123 | #[test] 124 | fn test_divide_zero() { 125 | assert_trace_present( 126 | "test_subjects_lib::arithmetic::divide_no_overflow", 127 | "attempt to divide by zero", 128 | &Release, 129 | ); 130 | } 131 | 132 | // Test if an divide by 0 check on a `%` operator is detected with the correct message. 133 | // This does only occur if the overflow check is optimized away 134 | #[test] 135 | fn test_remainder_zero() { 136 | assert_trace_present( 137 | "test_subjects_lib::arithmetic::remainder_no_overflow", 138 | "attempt to calculate the remainder with a divisor of zero", 139 | &Release, 140 | ); 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /lib/panic_analysis/tests/recognize_direct.rs: -------------------------------------------------------------------------------- 1 | // (C) COPYRIGHT 2018 TECHNOLUTION BV, GOUDA NL 2 | 3 | // Licensed under the Apache License, Version 2.0 or the MIT license 5 | // , at your 6 | // option. This file may not be copied, modified, or distributed 7 | // except according to those terms. 8 | 9 | #[cfg(test)] 10 | mod test { 11 | extern crate panic_analysis; 12 | extern crate test_common; 13 | 14 | use self::panic_analysis::*; 15 | use self::test_common::*; 16 | 17 | /// In this integration test we look at the 'direct' test subject 18 | /// This test subject has a main, in which 2 functions (foo and bar) are inlined. 19 | /// Also, bar panics. We test if this trace is found, with correct inline frames, and recognized as direct panic. 20 | #[test] 21 | pub fn test_recognize_inlined_direct_panics() { 22 | let path = test_common::get_test_subject_path("direct", &TestSubjectType::Debug); 23 | let binary_path = path.to_str().map(|x| x.to_string()); 24 | let options = AnalysisOptions { 25 | binary_path, 26 | crate_names: vec!["test_subjects".to_string()], 27 | full_crate_analysis: false, 28 | output_full_callgraph: false, 29 | output_filtered_callgraph: false, 30 | whitelisted_functions: vec![], 31 | }; 32 | 33 | let calls = find_panics(&options).unwrap(); 34 | assert_eq!(calls.calls.len(), 1); 35 | 36 | let direct_trace = &calls.calls[0]; 37 | assert_eq!(*direct_trace.pattern.borrow(), PanicPattern::DirectCall); 38 | 39 | let direct_outer_frames = direct_trace 40 | .backtrace 41 | .iter() 42 | .map(|x| x.procedure.borrow().linkage_name_demangled.to_owned()) 43 | .collect::>(); 44 | 45 | assert_eq!( 46 | direct_outer_frames, 47 | vec!["direct::main", "std::panicking::begin_panic"] 48 | ); 49 | 50 | let direct_inline_frames = direct_trace.backtrace[0] 51 | .outgoing_invocation 52 | .as_ref() 53 | .unwrap() 54 | .borrow() 55 | .frames 56 | .iter() 57 | .rev() 58 | .map(|inline| inline.function_name.to_owned()) 59 | .collect::>(); 60 | 61 | assert_eq!( 62 | direct_inline_frames, 63 | vec!["direct::main", "direct::foo", "direct::bar"] 64 | ); 65 | } 66 | 67 | /// In this integration test we look at the 'indirect' test subject 68 | /// This test subject has a main, in which 2 functions form a library (call_panic_indirect and call_panic_direct) are inlined. 69 | /// Also, call_panic_direct panics. We test if this trace is found, with correct inline frames, and but not recognized as direct panic. 70 | #[test] 71 | pub fn test_not_recognize_inlined_indirect_panics() { 72 | let path = test_common::get_test_subject_path("indirect", &TestSubjectType::Debug); 73 | let binary_path = path.to_str().map(|x| x.to_string()); 74 | let options = AnalysisOptions { 75 | binary_path, 76 | crate_names: vec!["test_subjects".to_string()], 77 | full_crate_analysis: false, 78 | output_full_callgraph: false, 79 | output_filtered_callgraph: false, 80 | whitelisted_functions: vec![], 81 | }; 82 | 83 | let calls = find_panics(&options).unwrap(); 84 | assert_eq!(calls.calls.len(), 1); 85 | 86 | let indirect_trace = &calls.calls[0]; 87 | assert_ne!(*indirect_trace.pattern.borrow(), PanicPattern::DirectCall); 88 | 89 | let indirect_outer_frames = indirect_trace 90 | .backtrace 91 | .iter() 92 | .map(|x| x.procedure.borrow().linkage_name_demangled.to_owned()) 93 | .collect::>(); 94 | 95 | assert_eq!( 96 | indirect_outer_frames, 97 | vec!["indirect::main", "std::panicking::begin_panic"] 98 | ); 99 | 100 | let indirect_inline_frames = indirect_trace.backtrace[0] 101 | .outgoing_invocation 102 | .as_ref() 103 | .unwrap() 104 | .borrow() 105 | .frames 106 | .iter() 107 | .rev() 108 | .map(|inline| inline.function_name.to_owned()) 109 | .collect::>(); 110 | 111 | assert_eq!( 112 | indirect_inline_frames, 113 | vec![ 114 | "indirect::main", 115 | "test_subjects_lib::inline::call_panic_indirect", 116 | "test_subjects_lib::inline::call_panic_direct", 117 | ] 118 | ); 119 | } 120 | 121 | /// In this integration test we look at the 'direct' test subject 122 | /// This test subject has a main, in which 2 functions (foo and bar) are inlined. 123 | /// Also, bar panics. We test if this trace is found, with correct inline frames, and recognized as direct panic. 124 | #[test] 125 | pub fn test_recognize_inlined_direct_panics_release() { 126 | let path = test_common::get_test_subject_path("direct", &TestSubjectType::Release); 127 | let binary_path = path.to_str().map(|x| x.to_string()); 128 | let options = AnalysisOptions { 129 | binary_path, 130 | crate_names: vec!["test_subjects".to_string()], 131 | full_crate_analysis: false, 132 | output_full_callgraph: false, 133 | output_filtered_callgraph: false, 134 | whitelisted_functions: vec![], 135 | }; 136 | 137 | let calls = find_panics(&options).unwrap(); 138 | assert_eq!(calls.calls.len(), 1); 139 | 140 | let direct_trace = &calls.calls[0]; 141 | assert_eq!(*direct_trace.pattern.borrow(), PanicPattern::DirectCall); 142 | 143 | let direct_outer_frames = direct_trace 144 | .backtrace 145 | .iter() 146 | .map(|x| x.procedure.borrow().linkage_name_demangled.to_owned()) 147 | .collect::>(); 148 | 149 | assert_eq!( 150 | direct_outer_frames, 151 | vec!["direct::main", "std::panicking::begin_panic"] 152 | ); 153 | 154 | let direct_inline_frames = direct_trace.backtrace[0] 155 | .outgoing_invocation 156 | .as_ref() 157 | .unwrap() 158 | .borrow() 159 | .frames 160 | .iter() 161 | .rev() 162 | .map(|inline| inline.function_name.to_owned()) 163 | .collect::>(); 164 | 165 | assert_eq!( 166 | direct_inline_frames, 167 | vec!["direct::main", "direct::foo", "direct::bar"] 168 | ); 169 | } 170 | 171 | /// In this integration test we look at the 'indirect' test subject 172 | /// This test subject has a main, in which 2 functions form a library (call_panic_indirect and call_panic_direct) are inlined. 173 | /// Also, call_panic_direct panics. We test if this trace is found, with correct inline frames, and but not recognized as direct panic. 174 | #[test] 175 | pub fn test_not_recognize_inlined_indirect_panics_release() { 176 | let path = test_common::get_test_subject_path("indirect", &TestSubjectType::Release); 177 | let binary_path = path.to_str().map(|x| x.to_string()); 178 | let options = AnalysisOptions { 179 | binary_path, 180 | crate_names: vec!["test_subjects".to_string()], 181 | full_crate_analysis: false, 182 | output_full_callgraph: false, 183 | output_filtered_callgraph: false, 184 | whitelisted_functions: vec![], 185 | }; 186 | 187 | let calls = find_panics(&options).unwrap(); 188 | assert_eq!(calls.calls.len(), 1); 189 | 190 | let indirect_trace = &calls.calls[0]; 191 | assert_ne!(*indirect_trace.pattern.borrow(), PanicPattern::DirectCall); 192 | 193 | let indirect_outer_frames = indirect_trace 194 | .backtrace 195 | .iter() 196 | .map(|x| x.procedure.borrow().linkage_name_demangled.to_owned()) 197 | .collect::>(); 198 | 199 | assert_eq!( 200 | indirect_outer_frames, 201 | vec!["indirect::main", "std::panicking::begin_panic"] 202 | ); 203 | 204 | let indirect_inline_frames = indirect_trace.backtrace[0] 205 | .outgoing_invocation 206 | .as_ref() 207 | .unwrap() 208 | .borrow() 209 | .frames 210 | .iter() 211 | .rev() 212 | .map(|inline| inline.function_name.to_owned()) 213 | .collect::>(); 214 | 215 | assert_eq!( 216 | indirect_inline_frames, 217 | vec![ 218 | "indirect::main", 219 | "test_subjects_lib::inline::call_panic_indirect", 220 | "test_subjects_lib::inline::call_panic_direct", 221 | ] 222 | ); 223 | } 224 | } 225 | -------------------------------------------------------------------------------- /lib/panic_analysis/tests/recognize_indexing.rs: -------------------------------------------------------------------------------- 1 | // (C) COPYRIGHT 2018 TECHNOLUTION BV, GOUDA NL 2 | 3 | // Licensed under the Apache License, Version 2.0 or the MIT license 5 | // , at your 6 | // option. This file may not be copied, modified, or distributed 7 | // except according to those terms. 8 | 9 | #[cfg(test)] 10 | mod test { 11 | extern crate panic_analysis; 12 | extern crate test_common; 13 | 14 | use self::panic_analysis::*; 15 | use self::test_common::*; 16 | 17 | /// In this integration test we look at the 'indexing' test subject 18 | /// This library has a dependency on test_subjects_lib::indexing, which does calls to `core::slice::SliceIndex<[T]>>::index`. 19 | /// We test if these 1 traces is found, and categorized correctly. 20 | #[test] 21 | pub fn test_recognize_indexing() { 22 | let path = test_common::get_test_subject_path("indexing", &TestSubjectType::Release); 23 | let binary_path = path.to_str().map(|x| x.to_string()); 24 | let options = AnalysisOptions { 25 | binary_path, 26 | crate_names: vec!["test_subjects".to_string()], 27 | full_crate_analysis: false, 28 | output_full_callgraph: false, 29 | output_filtered_callgraph: false, 30 | whitelisted_functions: vec![], 31 | }; 32 | 33 | let calls = find_panics(&options).unwrap(); 34 | 35 | let index_panics = calls 36 | .calls 37 | .iter() 38 | .filter(|c| *c.pattern.borrow() == PanicPattern::Indexing) 39 | .collect::>(); 40 | 41 | let other_panics = calls 42 | .calls 43 | .iter() 44 | .filter(|c| *c.pattern.borrow() != PanicPattern::Indexing) 45 | .collect::>(); 46 | 47 | assert_eq!(calls.calls.len(), 1); 48 | assert_eq!(index_panics.len(), 1); 49 | assert_eq!(other_panics.len(), 0); 50 | 51 | let index_traces = index_panics 52 | .iter() 53 | .map(|x| { 54 | x.backtrace 55 | .iter() 56 | .map(|entry| entry.procedure.borrow().linkage_name_demangled.to_owned()) 57 | .collect::>() 58 | }) 59 | .collect::>(); 60 | 61 | let outgoing_invoc = &index_panics[0].backtrace[0].outgoing_invocation; 62 | let inlines = &outgoing_invoc.as_ref().unwrap().borrow().frames; 63 | 64 | println!("{:?}", index_traces); 65 | 66 | assert!(index_traces.iter().any(|x| { 67 | let mut iter = x.iter(); 68 | iter.next().unwrap() == "indexing::call_index" 69 | && iter.next().unwrap() == "core::panicking::panic_bounds_check" 70 | && iter.next().unwrap() == "core::panicking::panic_fmt" 71 | })); 72 | 73 | let inline_names: Vec = inlines.iter().map(|x| x.function_name.clone()).collect(); 74 | 75 | assert_eq!( 76 | inline_names, 77 | vec![ 78 | ">::index", 79 | "core::slice:: for [T]>::index", 80 | " as core::ops::index::Index>::index", 81 | "indexing::call_index", 82 | ] 83 | ) 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /lib/panic_analysis/tests/recognize_unwrap.rs: -------------------------------------------------------------------------------- 1 | // (C) COPYRIGHT 2018 TECHNOLUTION BV, GOUDA NL 2 | 3 | // Licensed under the Apache License, Version 2.0 or the MIT license 5 | // , at your 6 | // option. This file may not be copied, modified, or distributed 7 | // except according to those terms. 8 | 9 | #[cfg(test)] 10 | mod test { 11 | extern crate panic_analysis; 12 | extern crate test_common; 13 | 14 | use self::panic_analysis::*; 15 | use self::test_common::*; 16 | 17 | /// In this integration test we look at the 'unwrap' test subject 18 | /// This library has a dependency on test_subjects_lib::unwrap, which does calls to `Option::unwrap()`, `Option::expect`, `Result::unwrap()` and a direct panic call 19 | /// We test if these 4 traces are found, and categorized correctly. 20 | #[test] 21 | pub fn test_recognize_unwraps() { 22 | let path = test_common::get_test_subject_path("unwrap", &TestSubjectType::Debug); 23 | let binary_path = path.to_str().map(|x| x.to_string()); 24 | let options = AnalysisOptions { 25 | binary_path, 26 | crate_names: vec!["test_subjects".to_string()], 27 | full_crate_analysis: false, 28 | output_full_callgraph: false, 29 | output_filtered_callgraph: false, 30 | whitelisted_functions: vec![], 31 | }; 32 | 33 | let calls = find_panics(&options).unwrap(); 34 | 35 | let unwrap_panics = calls 36 | .calls 37 | .iter() 38 | .filter(|c| *c.pattern.borrow() == PanicPattern::Unwrap) 39 | .collect::>(); 40 | let other_panics = calls 41 | .calls 42 | .iter() 43 | .filter(|c| *c.pattern.borrow() != PanicPattern::Unwrap) 44 | .collect::>(); 45 | 46 | assert_eq!(calls.calls.len(), 5); 47 | assert_eq!(unwrap_panics.len(), 4); 48 | assert_eq!(other_panics.len(), 1); 49 | 50 | let unwrap_traces = unwrap_panics 51 | .iter() 52 | .map(|x| { 53 | x.backtrace 54 | .iter() 55 | .map(|entry| entry.procedure.borrow().linkage_name_demangled.to_owned()) 56 | .take_while(|x| !x.starts_with("core")) 57 | .collect::>() 58 | }) 59 | .collect::>(); 60 | 61 | assert!(unwrap_traces.iter().any(|x| x == &vec![ 62 | "unwrap::call_unwrap", 63 | "test_subjects_lib::unwrap_calls::call_unwrap", 64 | ">::unwrap", 65 | ])); 66 | assert!(unwrap_traces.iter().any(|x| x == &vec![ 67 | "unwrap::call_expect", 68 | "test_subjects_lib::unwrap_calls::call_expect", 69 | ">::expect", 70 | ])); 71 | assert!(unwrap_traces.iter().any(|x| x == &vec![ 72 | "unwrap::call_option_expect", 73 | "test_subjects_lib::unwrap_calls::call_option_expect", 74 | ">::expect", 75 | ])); 76 | assert!(unwrap_traces.iter().any(|x| x == &vec![ 77 | "unwrap::call_unwrap_deep", 78 | "test_subjects_lib::unwrap_calls::call_unwrap_deep", 79 | "test_subjects_lib::unwrap_calls::call_unwrap_deep_2", 80 | "test_subjects_lib::unwrap_calls::call_unwrap_deep_3", 81 | ">::unwrap", 82 | ])); 83 | 84 | let other_trace = other_panics[0] 85 | .backtrace 86 | .iter() 87 | .map(|entry| entry.procedure.borrow().linkage_name_demangled.to_owned()) 88 | .collect::>(); 89 | 90 | assert_eq!( 91 | vec!["unwrap::panic_otherwise", "std::panicking::begin_panic"], 92 | other_trace 93 | ); 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /lib/test_common/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "test_common" 3 | version = "0.1.0" 4 | authors = [ 5 | "Bart van Schaick ", 6 | "Dominique van Cuilenborg ", 7 | "Fabian Stelmach ", 8 | "Aron Zwaan ", 9 | "Erwin Gribnau " 10 | ] 11 | 12 | [dependencies] 13 | gimli = "0.15.0" 14 | object = "0.7.0" 15 | elf = "0.0.10" 16 | capstone = { git = "https://github.com/capstone-rust/capstone-rs.git", rev = "dc10578aa27414afc93f0a87959b2f0c22dc66c3" } 17 | -------------------------------------------------------------------------------- /lib/test_common/build.rs: -------------------------------------------------------------------------------- 1 | // (C) COPYRIGHT 2018 TECHNOLUTION BV, GOUDA NL 2 | 3 | // Licensed under the Apache License, Version 2.0 or the MIT license 5 | // , at your 6 | // option. This file may not be copied, modified, or distributed 7 | // except according to those terms. 8 | 9 | /// This build script ensures the binaries of the test subjects (programs that are used as input for the tests) 10 | /// are build. Currently, we build 2 types of tests 11 | /// ## `test_subjects` 12 | /// The projects in this workspace build against the default toolchain. These subjects are 13 | /// used in tests that verify the tool works on a (new) particular Rust version. These tests perform regression testing on 14 | /// the tool itself as well as the Rust compiler. Changes in the Rust compiler that break the tool should be detected by tests 15 | /// on these projects. 16 | /// ## `test_subjects_stable_rustc` 17 | /// These tests build against a particular Rust version ('stable-2018-05-10'). Tests against these subjects may have stronger 18 | /// assumptions on the created binary. These tests do no regression testing on the Rust compiler (since the version is fixed) 19 | /// but do regression testing on the tool. 20 | use std::path::Path; 21 | use std::path::PathBuf; 22 | use std::process::Command; 23 | 24 | static RES_PATH: &str = "test_subjects"; 25 | static RES_PATH_STABLE_RUSTC: &str = "test_subjects_stable_rustc"; 26 | static STABLE_RUSTC_VERSION: &str = "stable-2018-05-10"; // 1.26.0 27 | 28 | static BUILD_MODE_ARGS: &[Option<&str>] = &[None, Some("--release")]; 29 | 30 | fn main() { 31 | // Build test_subjects on default toolchain for compiler regression testing 32 | build_subjects(RES_PATH, None); 33 | // Build test_subjects on toolchain 'stable-2018-05-10' for tool regression testing 34 | build_subjects(RES_PATH_STABLE_RUSTC, Some(STABLE_RUSTC_VERSION)); 35 | } 36 | 37 | /// Get the full path of `test_subjects` directory 38 | fn test_subject_dir(endpath: &str) -> PathBuf { 39 | // '${PWD}/lib/test_common' 40 | let current_dir = Path::new(env!("CARGO_MANIFEST_DIR")).to_path_buf(); 41 | let grandparent_dir = current_dir 42 | .parent() 43 | .expect("Current directory has no parent") 44 | .parent() 45 | .expect("Current directory has no grandparent"); 46 | 47 | // '${PWD}/ 48 | Path::join(grandparent_dir, Path::new(endpath)) 49 | } 50 | 51 | fn build_subjects(endpath: &str, fixed_cargo_version: Option<&str>) { 52 | let subjects = test_subject_dir(&endpath); 53 | let subjects = subjects.to_str().unwrap(); 54 | clean(subjects); 55 | build(subjects, fixed_cargo_version); 56 | } 57 | 58 | fn clean(subjects: &str) { 59 | let subjects_clean_status = Command::new("cargo") 60 | .current_dir(&subjects) 61 | .arg("clean") 62 | .status() 63 | .expect("Cleaning test subject dir did not produce any output"); 64 | 65 | if !subjects_clean_status.success() { 66 | panic!("Could not clean test subjects, manual intervention needed"); 67 | } 68 | } 69 | 70 | fn build(subjects: &str, fixed_cargo_version: Option<&str>) { 71 | BUILD_MODE_ARGS.iter().for_each(|arg| { 72 | let mut cargo = Command::new("cargo"); 73 | 74 | cargo.current_dir(&subjects); 75 | 76 | if let Some(ref version) = fixed_cargo_version { 77 | cargo.env("RUSTUP_TOOLCHAIN", version); 78 | } 79 | 80 | cargo.arg("build"); 81 | cargo.arg("--target"); 82 | cargo.arg("x86_64-unknown-linux-gnu"); 83 | 84 | if let Some(arg) = arg { 85 | cargo.arg(arg); 86 | } 87 | 88 | let subjects_build_status = cargo 89 | .status() 90 | .expect("Building of test subjects did not produce any output"); 91 | 92 | if !subjects_build_status.success() { 93 | panic!("Could not build test subjects, manual intervention needed"); 94 | } 95 | }) 96 | } 97 | -------------------------------------------------------------------------------- /lib/test_common/src/lib.rs: -------------------------------------------------------------------------------- 1 | // (C) COPYRIGHT 2018 TECHNOLUTION BV, GOUDA NL 2 | 3 | // Licensed under the Apache License, Version 2.0 or the MIT license 5 | // , at your 6 | // option. This file may not be copied, modified, or distributed 7 | // except according to those terms. 8 | 9 | extern crate capstone; 10 | extern crate gimli; 11 | extern crate object; 12 | 13 | use std::fs::File; 14 | use std::io::Error; 15 | use std::io::Read; 16 | use std::path::Path; 17 | use std::path::PathBuf; 18 | 19 | pub enum TestSubjectType { 20 | Debug, 21 | Release, 22 | DebugStableRustc, 23 | ReleaseStableRustc, 24 | } 25 | 26 | impl TestSubjectType { 27 | fn get_test_subject_path(&self) -> &str { 28 | match *self { 29 | TestSubjectType::Debug => "test_subjects/target/x86_64-unknown-linux-gnu/debug", 30 | TestSubjectType::Release => "test_subjects/target/x86_64-unknown-linux-gnu/release", 31 | TestSubjectType::DebugStableRustc => "test_subjects_stable_rustc/target/x86_64-unknown-linux-gnu/debug", 32 | TestSubjectType::ReleaseStableRustc => "test_subjects_stable_rustc/target/x86_64-unknown-linux-gnu/release", 33 | } 34 | } 35 | } 36 | 37 | /// Prepare the path of the executable of test subject `subject` 38 | pub fn get_test_subject_path(subject: &str, subject_type: &TestSubjectType) -> PathBuf { 39 | let current_dir = Path::new(env!("CARGO_MANIFEST_DIR")).to_path_buf(); 40 | let current_grandparent = current_dir 41 | .parent() 42 | .expect("Current executable path has no parent") 43 | .parent() 44 | .expect("Current executable path has no grandparent"); 45 | 46 | let executable_path = Path::join(current_grandparent, subject_type.get_test_subject_path()); 47 | 48 | Path::join(executable_path.as_path(), subject) 49 | } 50 | 51 | /// Load a test subject executable. Must be one of the crates in `test_subjects` 52 | pub fn load_test_binary(subject: &str, subject_type: &TestSubjectType) -> Result { 53 | let executable = get_test_subject_path(subject, subject_type); 54 | 55 | File::open(executable) 56 | } 57 | 58 | /// Load a test subject executable as a byte vector. Must be one of the crates in `test_subjects` 59 | pub fn load_test_binary_as_bytes( 60 | subject: &str, 61 | subject_type: &TestSubjectType, 62 | ) -> Result, Error> { 63 | let mut file = load_test_binary(subject, subject_type)?; 64 | 65 | let mut file_content = Vec::::new(); 66 | 67 | file.read_to_end(&mut file_content)?; 68 | 69 | Ok(file_content) 70 | } 71 | 72 | #[cfg(test)] 73 | mod tests { 74 | use super::*; 75 | 76 | #[test] 77 | fn load_test_subject_succeeds() { 78 | let result = load_test_binary(&"hello_world", &TestSubjectType::Debug); 79 | assert!(result.is_ok()); 80 | } 81 | 82 | #[test] 83 | fn load_test_subject_fails() { 84 | let result = load_test_binary(&"nonexistent_subject", &TestSubjectType::Debug); 85 | assert!(result.is_err()); 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/cmd_args.rs: -------------------------------------------------------------------------------- 1 | // (C) COPYRIGHT 2018 TECHNOLUTION BV, GOUDA NL 2 | 3 | // Licensed under the Apache License, Version 2.0 or the MIT license 5 | // , at your 6 | // option. This file may not be copied, modified, or distributed 7 | // except according to those terms. 8 | 9 | extern crate clap; 10 | extern crate panic_analysis; 11 | extern crate std; 12 | 13 | use config_file::parse_config; 14 | use errors::*; 15 | 16 | use self::panic_analysis::AnalysisOptions; 17 | 18 | use self::clap::App; 19 | use self::clap::Arg; 20 | use self::clap::ArgMatches; 21 | use self::clap::ErrorKind; 22 | 23 | use output::OutputOptions; 24 | 25 | use std::option::Option::Some; 26 | 27 | static CALL_GRAPH_BUILD_MODES: [&'static str; 2] = ["full", "filtered"]; 28 | 29 | fn parse_multiple_args(cmd_matches: &ArgMatches, name: &str) -> Vec { 30 | match cmd_matches.values_of(name) { 31 | Some(x) => x.into_iter().map(|x| x.to_string()).collect(), 32 | None => Vec::new(), 33 | } 34 | } 35 | 36 | pub fn get_args() -> Result<(AnalysisOptions, OutputOptions)> { 37 | let cmd_matches_opt = get_app_definition().get_matches_safe(); 38 | 39 | let cmd_matches = match cmd_matches_opt { 40 | Ok(matches) => matches, 41 | Err(error) => { 42 | eprintln!("{}", error.message); 43 | match error.kind { 44 | ErrorKind::HelpDisplayed => std::process::exit(0), 45 | ErrorKind::VersionDisplayed => std::process::exit(0), 46 | _ => std::process::exit(101), 47 | } 48 | } 49 | }; 50 | 51 | let crate_names = parse_multiple_args(&cmd_matches, "crates"); 52 | 53 | let callgraph_outputs = parse_multiple_args(&cmd_matches, "callgraph"); 54 | 55 | let config_opt = cmd_matches.value_of("config"); 56 | let required = config_opt.is_some(); 57 | 58 | let file_options = parse_config(config_opt.unwrap_or("rustig.toml"), required)?; 59 | 60 | let rustig_options = AnalysisOptions { 61 | binary_path: Some(cmd_matches.value_of("binary").unwrap().to_string()), // Required by clap, can safely be unwrapped. 62 | crate_names, 63 | whitelisted_functions: file_options.function_whitelists, 64 | output_filtered_callgraph: callgraph_outputs.iter().any(|output| output == "filtered"), 65 | output_full_callgraph: callgraph_outputs.iter().any(|output| output == "full"), 66 | full_crate_analysis: cmd_matches.is_present("full_crate_analysis"), 67 | }; 68 | 69 | let output_options = OutputOptions { 70 | verbose: cmd_matches.is_present("verbose"), 71 | silent: cmd_matches.is_present("silent"), 72 | json: cmd_matches.is_present("json-stream"), 73 | }; 74 | 75 | Ok((rustig_options, output_options)) 76 | } 77 | 78 | fn get_app_definition<'a, 'b>() -> App<'a, 'b> { 79 | App::new("Rust don't panic") 80 | // Argument accepting the path to the binary to analyze 81 | // Note that this is a parameter, since we might build from Cargo projects in the future 82 | // In that case this argument will not be required anymore. 83 | // To maintain compatibility, we decided to make it an parameter immediately 84 | .arg( 85 | Arg::with_name("binary") 86 | .short("b") 87 | .long("binary") 88 | .value_name("FILE") 89 | .help("Path to binary file to analyze") 90 | .required(true) // Needs to be removed if we decide to compile our own binaries in the future. 91 | .takes_value(true), 92 | ) 93 | // Right now, crates are printed in the output 94 | // Maybe we should once make a subcommand that prints all crates and versions 95 | .arg( 96 | Arg::with_name("crates") 97 | .multiple(true) 98 | .short("c") 99 | .long("crates") 100 | .value_name("CRATES") 101 | .help("Names of the compilation unit which should be analyzed. If not provided, the crate of the entry points will be used"), 102 | ) 103 | .arg( 104 | Arg::with_name("verbose") 105 | .short("v") 106 | .long("verbose") 107 | .conflicts_with("silent") 108 | .help("Turn on verbose mode for full stack traces of panic calls"), 109 | ) 110 | .arg( 111 | Arg::with_name("json-stream") 112 | .long("json-stream") 113 | .conflicts_with("silent") 114 | .help("Output full stack traces of panic calls into JSON"), 115 | ) 116 | .arg( 117 | Arg::with_name("config") 118 | .long("config") 119 | .help("Path to rustig! configuration file (default: rustig.toml)") 120 | .takes_value(true), 121 | ) 122 | .arg( 123 | Arg::with_name("full_crate_analysis") 124 | .short("f") 125 | .long("full-crate-analysis") 126 | .help("Analyze all functions in analysis target, instead of entry points only"), 127 | ) 128 | .arg( 129 | Arg::with_name("silent") 130 | .short("s") 131 | .long("silent") 132 | .conflicts_with("verbose") 133 | .conflicts_with("json") 134 | .help("Turn on silent mode to not print anything"), 135 | ) 136 | .arg( 137 | Arg::with_name("callgraph") 138 | .multiple(true) 139 | .min_values(1) 140 | .value_name("CALLGRAPH") 141 | .long("callgraph") 142 | .short("g") 143 | .help("Write a callgraph of the given binary to a file. The output filename will be: `rustig-callgraph-{projectname}-{type}`, where `type` is either `full` or `filtered`. The full callgraph contains all function calls that are detected by rustig!, while filtered callgraph only contains paths that possibly lead to panic calls") 144 | .possible_values(&CALL_GRAPH_BUILD_MODES), 145 | ) 146 | } 147 | 148 | #[cfg(test)] 149 | mod test { 150 | extern crate assert_cli; 151 | 152 | /// Test that an error is printed if no '--binary' command line parameter is given 153 | #[test] 154 | fn test_no_binary_provided() { 155 | // Execute our build with no cmd parameters 156 | assert_cli::Assert::main_binary() 157 | // Assert that the tool indicates the '--binary' argument is not given 158 | .stderr().contains("error: The following required arguments were not provided:") 159 | .stderr().contains("--binary ") 160 | .fails_with(101) 161 | .unwrap(); 162 | } 163 | } 164 | -------------------------------------------------------------------------------- /src/config_file.rs: -------------------------------------------------------------------------------- 1 | // (C) COPYRIGHT 2018 TECHNOLUTION BV, GOUDA NL 2 | 3 | // Licensed under the Apache License, Version 2.0 or the MIT license 5 | // , at your 6 | // option. This file may not be copied, modified, or distributed 7 | // except according to those terms. 8 | 9 | extern crate panic_analysis; 10 | extern crate toml; 11 | 12 | use errors::*; 13 | use panic_analysis::{FunctionWhiteListEntry, FunctionWhitelistCrateVersion}; 14 | 15 | use std::fs::File; 16 | use std::io::Read; 17 | use std::path::Path; 18 | 19 | #[derive(Clone, Debug, Default)] 20 | pub struct ConfigFileOptions { 21 | pub function_whitelists: Vec, 22 | } 23 | 24 | #[derive(Deserialize)] 25 | pub struct FunctionWhiteListTomlEntry { 26 | function_name: String, 27 | crate_name: Option, 28 | crate_version: Option, 29 | strict: Option, 30 | } 31 | 32 | #[derive(Deserialize)] 33 | pub struct Config { 34 | whitelisted_functions: Vec, 35 | } 36 | 37 | impl From for FunctionWhiteListEntry { 38 | fn from(toml: FunctionWhiteListTomlEntry) -> Self { 39 | let strict = toml.strict.unwrap_or(false); 40 | FunctionWhiteListEntry { 41 | function_name: toml.function_name, 42 | crate_name: toml.crate_name, 43 | crate_version: toml.crate_version 44 | .map(|x| { 45 | if strict { 46 | FunctionWhitelistCrateVersion::Strict(x) 47 | } else { 48 | FunctionWhitelistCrateVersion::Loose(x) 49 | } 50 | }) 51 | .unwrap_or(FunctionWhitelistCrateVersion::None), 52 | } 53 | } 54 | } 55 | 56 | pub fn parse_config(path: &str, required: bool) -> Result { 57 | let path = Path::new(path); 58 | 59 | if !path.exists() { 60 | if required { 61 | return Err(ErrorKind::ConfigLoad( 62 | path.to_str().unwrap_or("").to_string(), 63 | Some("File does not exist".to_string()), 64 | ).into()); 65 | } 66 | return Ok(ConfigFileOptions::default()); 67 | } 68 | 69 | // Read configuration file 70 | let mut file = File::open(path).chain_err(|| { 71 | ErrorKind::ConfigLoad(path.to_str().unwrap_or("").to_string(), None) 72 | })?; 73 | 74 | let mut file_content = Vec::::new(); 75 | file.read_to_end(&mut file_content).chain_err(|| { 76 | ErrorKind::ConfigLoad(path.to_str().unwrap_or("").to_string(), None) 77 | })?; 78 | 79 | let config: Config = toml::de::from_slice(&file_content).map_err(|err| { 80 | ErrorKind::ConfigLoad( 81 | path.to_str().unwrap_or("").to_string(), 82 | Some(err.to_string()), 83 | ) 84 | })?; 85 | 86 | Ok(ConfigFileOptions { 87 | function_whitelists: config 88 | .whitelisted_functions 89 | .into_iter() 90 | .map(FunctionWhiteListEntry::from) 91 | .collect(), 92 | }) 93 | } 94 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | // (C) COPYRIGHT 2018 TECHNOLUTION BV, GOUDA NL 2 | 3 | // Licensed under the Apache License, Version 2.0 or the MIT license 5 | // , at your 6 | // option. This file may not be copied, modified, or distributed 7 | // except according to those terms. 8 | 9 | //! This is the command-line interface to the panic_analysis crate. Details about the command-line options can be 10 | //! obtained by running `rustig --help` on a build or by visiting the 11 | //! [Github documentation](https://github.com/Technolution/rustig). 12 | //! 13 | 14 | #![recursion_limit="128"] 15 | 16 | #[macro_use] 17 | extern crate serde_derive; 18 | extern crate panic_analysis; 19 | extern crate toml; 20 | #[macro_use] 21 | extern crate error_chain; 22 | #[macro_use] 23 | extern crate serde_json; 24 | 25 | mod cmd_args; 26 | mod config_file; 27 | mod output; 28 | 29 | // AZ: error_chain uses #[allow(unused_doc_comment)], which has been rename to #[allow(unused_doc_comments)] 30 | #[allow(renamed_and_removed_lints)] 31 | pub mod errors { 32 | error_chain!{ 33 | errors{ 34 | ConfigLoad(path: String, reason: Option) { 35 | description("Config file not found") 36 | display("Unable to read config file `{}`{}", path, reason.as_ref().map(|x| format!(": {}", x)).unwrap_or_else(|| "".to_string())) 37 | } 38 | } 39 | } 40 | } 41 | 42 | use std::process; 43 | use std::result::Result::Ok; 44 | 45 | /// CLI entrypoint 46 | pub fn main() { 47 | // Parse cmd arguments 48 | let (cmd_args, output_options) = match cmd_args::get_args() { 49 | Err(e) => { 50 | eprintln!("{}", e); 51 | process::exit(101); 52 | } 53 | Ok(r) => r, 54 | }; 55 | 56 | // Execute analysis 57 | match panic_analysis::find_panics(&cmd_args) { 58 | Err(e) => { 59 | eprintln!("{}", e); 60 | process::exit(101); 61 | } 62 | Ok(collection) => { 63 | output::print_results(&output_options, &collection); 64 | 65 | // If a panic path is found, we exit with code 1 66 | // This enables integration with CI tools 67 | if !collection.calls.is_empty() { 68 | process::exit(1); 69 | } 70 | } 71 | } 72 | } 73 | 74 | #[cfg(test)] 75 | mod test { 76 | extern crate assert_cli; 77 | extern crate test_common; 78 | 79 | /// Test if correct exit code 1 is returned when 1 or more panics are found 80 | #[test] 81 | fn test_panics_found() { 82 | let path = 83 | test_common::get_test_subject_path("lib_calls", &test_common::TestSubjectType::Debug) 84 | .to_str() 85 | .unwrap() 86 | .to_string(); 87 | 88 | assert_cli::Assert::main_binary() 89 | .with_args(&["-b", &path, "-c", "test_subjects", "-s"]) 90 | .fails_with(1) 91 | .unwrap(); 92 | } 93 | 94 | /// Test if correct exit code 0 is returned when no panics are found 95 | #[test] 96 | fn test_no_panics_found() { 97 | let path = 98 | test_common::get_test_subject_path("empty", &test_common::TestSubjectType::Debug) 99 | .to_str() 100 | .unwrap() 101 | .to_string(); 102 | 103 | assert_cli::Assert::main_binary() 104 | .with_args(&["-b", &path, "-c", "test_subjects", "-s"]) 105 | .succeeds() 106 | .unwrap(); 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /test_subjects/.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | /target 3 | **/*.rs.bk 4 | -------------------------------------------------------------------------------- /test_subjects/Cargo.lock: -------------------------------------------------------------------------------- 1 | [[package]] 2 | name = "arithmetic" 3 | version = "0.1.0" 4 | dependencies = [ 5 | "test_subjects_lib 0.1.0", 6 | ] 7 | 8 | [[package]] 9 | name = "binary_search" 10 | version = "0.1.0" 11 | 12 | [[package]] 13 | name = "byteorder" 14 | version = "1.2.3" 15 | source = "registry+https://github.com/rust-lang/crates.io-index" 16 | 17 | [[package]] 18 | name = "capturing_closure_invocation" 19 | version = "0.1.0" 20 | 21 | [[package]] 22 | name = "cfg-if" 23 | version = "0.1.3" 24 | source = "registry+https://github.com/rust-lang/crates.io-index" 25 | 26 | [[package]] 27 | name = "chrono" 28 | version = "0.4.2" 29 | source = "registry+https://github.com/rust-lang/crates.io-index" 30 | dependencies = [ 31 | "num-integer 0.1.38 (registry+https://github.com/rust-lang/crates.io-index)", 32 | "num-traits 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)", 33 | "time 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)", 34 | ] 35 | 36 | [[package]] 37 | name = "dep" 38 | version = "0.1.0" 39 | dependencies = [ 40 | "log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", 41 | "simplelog 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)", 42 | ] 43 | 44 | [[package]] 45 | name = "direct" 46 | version = "0.1.0" 47 | 48 | [[package]] 49 | name = "empty" 50 | version = "0.1.0" 51 | 52 | [[package]] 53 | name = "hello_world" 54 | version = "0.1.0" 55 | 56 | [[package]] 57 | name = "indexing" 58 | version = "0.1.0" 59 | dependencies = [ 60 | "test_subjects_lib 0.1.0", 61 | ] 62 | 63 | [[package]] 64 | name = "indirect" 65 | version = "0.1.0" 66 | dependencies = [ 67 | "test_subjects_lib 0.1.0", 68 | ] 69 | 70 | [[package]] 71 | name = "kernel32-sys" 72 | version = "0.2.2" 73 | source = "registry+https://github.com/rust-lang/crates.io-index" 74 | dependencies = [ 75 | "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", 76 | "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", 77 | ] 78 | 79 | [[package]] 80 | name = "lib_calls" 81 | version = "0.1.0" 82 | dependencies = [ 83 | "test_subjects_lib 0.1.0", 84 | ] 85 | 86 | [[package]] 87 | name = "libc" 88 | version = "0.2.41" 89 | source = "registry+https://github.com/rust-lang/crates.io-index" 90 | 91 | [[package]] 92 | name = "log" 93 | version = "0.3.9" 94 | source = "registry+https://github.com/rust-lang/crates.io-index" 95 | dependencies = [ 96 | "log 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", 97 | ] 98 | 99 | [[package]] 100 | name = "log" 101 | version = "0.4.1" 102 | source = "registry+https://github.com/rust-lang/crates.io-index" 103 | dependencies = [ 104 | "cfg-if 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", 105 | ] 106 | 107 | [[package]] 108 | name = "multi_dep" 109 | version = "0.1.0" 110 | dependencies = [ 111 | "dep 0.1.0", 112 | "log 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", 113 | "simplelog 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)", 114 | ] 115 | 116 | [[package]] 117 | name = "num-integer" 118 | version = "0.1.38" 119 | source = "registry+https://github.com/rust-lang/crates.io-index" 120 | dependencies = [ 121 | "num-traits 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)", 122 | ] 123 | 124 | [[package]] 125 | name = "num-traits" 126 | version = "0.2.4" 127 | source = "registry+https://github.com/rust-lang/crates.io-index" 128 | 129 | [[package]] 130 | name = "redox_syscall" 131 | version = "0.1.40" 132 | source = "registry+https://github.com/rust-lang/crates.io-index" 133 | 134 | [[package]] 135 | name = "simplelog" 136 | version = "0.4.4" 137 | source = "registry+https://github.com/rust-lang/crates.io-index" 138 | dependencies = [ 139 | "log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", 140 | "term 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", 141 | "time 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)", 142 | ] 143 | 144 | [[package]] 145 | name = "simplelog" 146 | version = "0.5.2" 147 | source = "registry+https://github.com/rust-lang/crates.io-index" 148 | dependencies = [ 149 | "chrono 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", 150 | "log 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", 151 | "term 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", 152 | ] 153 | 154 | [[package]] 155 | name = "term" 156 | version = "0.4.6" 157 | source = "registry+https://github.com/rust-lang/crates.io-index" 158 | dependencies = [ 159 | "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", 160 | "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", 161 | ] 162 | 163 | [[package]] 164 | name = "term" 165 | version = "0.5.1" 166 | source = "registry+https://github.com/rust-lang/crates.io-index" 167 | dependencies = [ 168 | "byteorder 1.2.3 (registry+https://github.com/rust-lang/crates.io-index)", 169 | "winapi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", 170 | ] 171 | 172 | [[package]] 173 | name = "test_subjects_lib" 174 | version = "0.1.0" 175 | 176 | [[package]] 177 | name = "time" 178 | version = "0.1.40" 179 | source = "registry+https://github.com/rust-lang/crates.io-index" 180 | dependencies = [ 181 | "libc 0.2.41 (registry+https://github.com/rust-lang/crates.io-index)", 182 | "redox_syscall 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)", 183 | "winapi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", 184 | ] 185 | 186 | [[package]] 187 | name = "trait_invocation" 188 | version = "0.1.0" 189 | 190 | [[package]] 191 | name = "unwrap" 192 | version = "0.1.0" 193 | dependencies = [ 194 | "test_subjects_lib 0.1.0", 195 | ] 196 | 197 | [[package]] 198 | name = "winapi" 199 | version = "0.2.8" 200 | source = "registry+https://github.com/rust-lang/crates.io-index" 201 | 202 | [[package]] 203 | name = "winapi" 204 | version = "0.3.4" 205 | source = "registry+https://github.com/rust-lang/crates.io-index" 206 | dependencies = [ 207 | "winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 208 | "winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 209 | ] 210 | 211 | [[package]] 212 | name = "winapi-build" 213 | version = "0.1.1" 214 | source = "registry+https://github.com/rust-lang/crates.io-index" 215 | 216 | [[package]] 217 | name = "winapi-i686-pc-windows-gnu" 218 | version = "0.4.0" 219 | source = "registry+https://github.com/rust-lang/crates.io-index" 220 | 221 | [[package]] 222 | name = "winapi-x86_64-pc-windows-gnu" 223 | version = "0.4.0" 224 | source = "registry+https://github.com/rust-lang/crates.io-index" 225 | 226 | [metadata] 227 | "checksum byteorder 1.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "74c0b906e9446b0a2e4f760cdb3fa4b2c48cdc6db8766a845c54b6ff063fd2e9" 228 | "checksum cfg-if 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "405216fd8fe65f718daa7102ea808a946b6ce40c742998fbfd3463645552de18" 229 | "checksum chrono 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "1cce36c92cb605414e9b824f866f5babe0a0368e39ea07393b9b63cf3844c0e6" 230 | "checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" 231 | "checksum libc 0.2.41 (registry+https://github.com/rust-lang/crates.io-index)" = "ac8ebf8343a981e2fa97042b14768f02ed3e1d602eac06cae6166df3c8ced206" 232 | "checksum log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "e19e8d5c34a3e0e2223db8e060f9e8264aeeb5c5fc64a4ee9965c062211c024b" 233 | "checksum log 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "89f010e843f2b1a31dbd316b3b8d443758bc634bed37aabade59c686d644e0a2" 234 | "checksum num-integer 0.1.38 (registry+https://github.com/rust-lang/crates.io-index)" = "6ac0ea58d64a89d9d6b7688031b3be9358d6c919badcf7fbb0527ccfd891ee45" 235 | "checksum num-traits 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)" = "775393e285254d2f5004596d69bb8bc1149754570dcc08cf30cabeba67955e28" 236 | "checksum redox_syscall 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)" = "c214e91d3ecf43e9a4e41e578973adeb14b474f2bee858742d127af75a0112b1" 237 | "checksum simplelog 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "24b615b1a3cc51ffa565d9a1d0cfcc49fe7d64737ada84eca284cddb0292d125" 238 | "checksum simplelog 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9cc12b39fdf4c9a07f88bffac2d628f0118ed5ac077a4b0feece61fadf1429e5" 239 | "checksum term 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "fa63644f74ce96fbeb9b794f66aff2a52d601cbd5e80f4b97123e3899f4570f1" 240 | "checksum term 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "5e6b677dd1e8214ea1ef4297f85dbcbed8e8cdddb561040cc998ca2551c37561" 241 | "checksum time 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)" = "d825be0eb33fda1a7e68012d51e9c7f451dc1a69391e7fdc197060bb8c56667b" 242 | "checksum winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" 243 | "checksum winapi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "04e3bd221fcbe8a271359c04f21a76db7d0c6028862d1bb5512d85e1e2eb5bb3" 244 | "checksum winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" 245 | "checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 246 | "checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 247 | -------------------------------------------------------------------------------- /test_subjects/Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | 3 | members = [ 4 | "hello_world", 5 | "empty", 6 | "unwrap", 7 | "binary_search", 8 | "lib_calls", 9 | "trait_invocation", 10 | "capturing_closure_invocation", 11 | "multi_dep", 12 | "dep", 13 | "indexing", 14 | "direct", 15 | "indirect", 16 | "arithmetic", 17 | ] 18 | 19 | [profile.release] 20 | debug = true 21 | -------------------------------------------------------------------------------- /test_subjects/README.md: -------------------------------------------------------------------------------- 1 | # Test subjects 2 | This project is a workspace that contains multiple projects used for testing of the `rustig` crate. These projects are compiled with the default `rustc` version (use command: `rustup show` to check). 3 | 4 | 5 | ## Adding new projects 6 | In order to add a new project, use the command: `cargo new --bin $projectname$`, where `$projectname$` is the name of the new test subject. 7 | 8 | Additionally, the project needs to be added to the `Cargo.toml` file. In order to do this, extend the `Cargo.toml` file by adding a new value to the `members` field, with the value of `"$projectname$"`. 9 | 10 | Note that adding, removing or changing a test subject requires a clean build to work properly. 11 | -------------------------------------------------------------------------------- /test_subjects/arithmetic/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "arithmetic" 3 | version = "0.1.0" 4 | authors = [ 5 | "Bart van Schaick ", 6 | "Dominique van Cuilenborg ", 7 | "Fabian Stelmach ", 8 | "Aron Zwaan ", 9 | "Erwin Gribnau " 10 | ] 11 | 12 | [dependencies] 13 | test_subjects_lib = {path = "../../test_subjects_lib/"} 14 | -------------------------------------------------------------------------------- /test_subjects/arithmetic/src/main.rs: -------------------------------------------------------------------------------- 1 | // (C) COPYRIGHT 2018 TECHNOLUTION BV, GOUDA NL 2 | 3 | // Licensed under the Apache License, Version 2.0 or the MIT license 5 | // , at your 6 | // option. This file may not be copied, modified, or distributed 7 | // except according to those terms. 8 | 9 | extern crate test_subjects_lib; 10 | 11 | use test_subjects_lib::arithmetic; 12 | 13 | fn main() { 14 | let a = arithmetic::add(2, 4); // 6 15 | let b = arithmetic::subtract(42, a); // 36 16 | let c = arithmetic::divide(b, 2); // 18 17 | let d = arithmetic::remainder(c, 10); // 8 18 | let e = arithmetic::shl(d, 2); // 2 19 | let f = arithmetic::shr(e, 3); // 16 20 | let g = match arithmetic::divide_no_overflow(f, 4) { 21 | Some(val) => val, 22 | None => 4, 23 | }; // 4 24 | let h = match arithmetic::remainder_no_overflow(g, 3) { 25 | Some(val) => val, 26 | None => 1, 27 | }; // 1 28 | let i = arithmetic::multiply(h, 16); // 16 29 | let j = match arithmetic::remainder_safe(i, 10) { 30 | Some(val) => val, 31 | None => 6, 32 | }; // 6 33 | let l = match arithmetic::divide_safe(j, 3) { 34 | Some(val) => val, 35 | None => 2, 36 | }; // 2 37 | arithmetic::equal(l, 2); 38 | } 39 | -------------------------------------------------------------------------------- /test_subjects/binary_search/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "binary_search" 3 | version = "0.1.0" 4 | authors = [ 5 | "Bart van Schaick ", 6 | "Dominique van Cuilenborg ", 7 | "Fabian Stelmach ", 8 | "Aron Zwaan ", 9 | "Erwin Gribnau " 10 | ] 11 | -------------------------------------------------------------------------------- /test_subjects/binary_search/src/main.rs: -------------------------------------------------------------------------------- 1 | // (C) COPYRIGHT 2018 TECHNOLUTION BV, GOUDA NL 2 | 3 | // Licensed under the Apache License, Version 2.0 or the MIT license 5 | // , at your 6 | // option. This file may not be copied, modified, or distributed 7 | // except according to those terms. 8 | 9 | use std::cmp::Ordering::*; 10 | #[inline(never)] 11 | pub fn binary_search(arr: &[T], elem: &T) -> Option { 12 | let mut size = arr.len(); 13 | let mut base = 0; 14 | 15 | while size > 0 { 16 | size /= 2; 17 | let mid = base + size; 18 | 19 | base = match arr[mid].cmp(elem) { 20 | Less => mid + 1, 21 | Greater => base, 22 | Equal => return Some(mid), 23 | }; 24 | } 25 | None 26 | } 27 | 28 | fn main() { 29 | binary_search(&[1, 2, 3], &2); 30 | } 31 | -------------------------------------------------------------------------------- /test_subjects/capturing_closure_invocation/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "capturing_closure_invocation" 3 | version = "0.1.0" 4 | authors = [ 5 | "Bart van Schaick ", 6 | "Dominique van Cuilenborg ", 7 | "Fabian Stelmach ", 8 | "Aron Zwaan ", 9 | "Erwin Gribnau " 10 | ] 11 | 12 | [dependencies] 13 | -------------------------------------------------------------------------------- /test_subjects/capturing_closure_invocation/src/main.rs: -------------------------------------------------------------------------------- 1 | // (C) COPYRIGHT 2018 TECHNOLUTION BV, GOUDA NL 2 | 3 | // Licensed under the Apache License, Version 2.0 or the MIT license 5 | // , at your 6 | // option. This file may not be copied, modified, or distributed 7 | // except according to those terms. 8 | 9 | fn invoke_clos(func: Box ()>) { 10 | func(); 11 | } 12 | 13 | fn main() { 14 | let message = "Hello world!"; 15 | let func = move || println!("{}", message); 16 | invoke_clos(Box::new(func)); 17 | } 18 | -------------------------------------------------------------------------------- /test_subjects/dep/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "dep" 3 | version = "0.1.0" 4 | authors = [ 5 | "Bart van Schaick ", 6 | "Dominique van Cuilenborg ", 7 | "Fabian Stelmach ", 8 | "Aron Zwaan ", 9 | "Erwin Gribnau " 10 | ] 11 | 12 | [dependencies] 13 | simplelog = "0.4.4" 14 | log = "0.3.0" 15 | -------------------------------------------------------------------------------- /test_subjects/dep/src/lib.rs: -------------------------------------------------------------------------------- 1 | // (C) COPYRIGHT 2018 TECHNOLUTION BV, GOUDA NL 2 | 3 | // Licensed under the Apache License, Version 2.0 or the MIT license 5 | // , at your 6 | // option. This file may not be copied, modified, or distributed 7 | // except according to those terms. 8 | 9 | extern crate log; 10 | pub extern crate simplelog as d2; 11 | 12 | use d2::*; 13 | use log::Log; 14 | use log::*; 15 | 16 | /// This test subject is used to make sure the multi_dep has two different dependencies on `simplelog`. 17 | /// In this function, a call to `TermLogger::new` of version 0.4.4 is made. 18 | #[inline(never)] 19 | pub fn baz() { 20 | let logger = TermLogger::new(LogLevelFilter::Info, Config::default()); 21 | } 22 | -------------------------------------------------------------------------------- /test_subjects/direct/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "direct" 3 | version = "0.1.0" 4 | authors = [ 5 | "Bart van Schaick ", 6 | "Dominique van Cuilenborg ", 7 | "Fabian Stelmach ", 8 | "Aron Zwaan ", 9 | "Erwin Gribnau " 10 | ] 11 | 12 | [dependencies] 13 | -------------------------------------------------------------------------------- /test_subjects/direct/src/main.rs: -------------------------------------------------------------------------------- 1 | // (C) COPYRIGHT 2018 TECHNOLUTION BV, GOUDA NL 2 | 3 | // Licensed under the Apache License, Version 2.0 or the MIT license 5 | // , at your 6 | // option. This file may not be copied, modified, or distributed 7 | // except according to those terms. 8 | 9 | fn main() { 10 | foo(); 11 | } 12 | 13 | #[inline(always)] 14 | fn foo() { 15 | bar(); 16 | } 17 | 18 | #[inline(always)] 19 | fn bar() { 20 | panic!("Panic from bar"); 21 | } 22 | -------------------------------------------------------------------------------- /test_subjects/empty/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "empty" 3 | version = "0.1.0" 4 | authors = [ 5 | "Bart van Schaick ", 6 | "Dominique van Cuilenborg ", 7 | "Fabian Stelmach ", 8 | "Aron Zwaan ", 9 | "Erwin Gribnau " 10 | ] 11 | 12 | [dependencies] 13 | -------------------------------------------------------------------------------- /test_subjects/empty/src/main.rs: -------------------------------------------------------------------------------- 1 | // (C) COPYRIGHT 2018 TECHNOLUTION BV, GOUDA NL 2 | 3 | // Licensed under the Apache License, Version 2.0 or the MIT license 5 | // , at your 6 | // option. This file may not be copied, modified, or distributed 7 | // except according to those terms. 8 | 9 | fn main() {} 10 | -------------------------------------------------------------------------------- /test_subjects/hello_world/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "hello_world" 3 | version = "0.1.0" 4 | authors = [ 5 | "Bart van Schaick ", 6 | "Dominique van Cuilenborg ", 7 | "Fabian Stelmach ", 8 | "Aron Zwaan ", 9 | "Erwin Gribnau " 10 | ] 11 | 12 | [dependencies] 13 | -------------------------------------------------------------------------------- /test_subjects/hello_world/src/main.rs: -------------------------------------------------------------------------------- 1 | // (C) COPYRIGHT 2018 TECHNOLUTION BV, GOUDA NL 2 | 3 | // Licensed under the Apache License, Version 2.0 or the MIT license 5 | // , at your 6 | // option. This file may not be copied, modified, or distributed 7 | // except according to those terms. 8 | 9 | fn main() { 10 | println!("Hello, world!"); 11 | } 12 | -------------------------------------------------------------------------------- /test_subjects/indexing/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "indexing" 3 | version = "0.1.0" 4 | authors = [ 5 | "Bart van Schaick ", 6 | "Dominique van Cuilenborg ", 7 | "Fabian Stelmach ", 8 | "Aron Zwaan ", 9 | "Erwin Gribnau " 10 | ] 11 | 12 | [dependencies] 13 | test_subjects_lib = {path = "../../test_subjects_lib/"} 14 | -------------------------------------------------------------------------------- /test_subjects/indexing/src/main.rs: -------------------------------------------------------------------------------- 1 | // (C) COPYRIGHT 2018 TECHNOLUTION BV, GOUDA NL 2 | 3 | // Licensed under the Apache License, Version 2.0 or the MIT license 5 | // , at your 6 | // option. This file may not be copied, modified, or distributed 7 | // except according to those terms. 8 | 9 | extern crate test_subjects_lib; 10 | 11 | /// This test subject builds to en executable that indexes a `Vec` in several different ways. 12 | /// It is used in the /panic_analysis/tests/recognize_indexing.rs integration tests 13 | fn main() { 14 | let vec = Vec::new(); 15 | call_index(vec); 16 | call_correct(); 17 | } 18 | 19 | #[inline(never)] 20 | fn call_index(vec: Vec) -> usize { 21 | vec[3] 22 | } 23 | 24 | #[inline(never)] 25 | fn call_correct() -> usize { 26 | let vec_other = vec![2, 3, 7]; 27 | vec_other[1] 28 | } 29 | -------------------------------------------------------------------------------- /test_subjects/indirect/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "indirect" 3 | version = "0.1.0" 4 | authors = [ 5 | "Bart van Schaick ", 6 | "Dominique van Cuilenborg ", 7 | "Fabian Stelmach ", 8 | "Aron Zwaan ", 9 | "Erwin Gribnau " 10 | ] 11 | 12 | [dependencies] 13 | test_subjects_lib = {path = "../../test_subjects_lib/"} 14 | -------------------------------------------------------------------------------- /test_subjects/indirect/src/main.rs: -------------------------------------------------------------------------------- 1 | // (C) COPYRIGHT 2018 TECHNOLUTION BV, GOUDA NL 2 | 3 | // Licensed under the Apache License, Version 2.0 or the MIT license 5 | // , at your 6 | // option. This file may not be copied, modified, or distributed 7 | // except according to those terms. 8 | 9 | extern crate test_subjects_lib; 10 | 11 | fn main() { 12 | test_subjects_lib::inline::call_panic_indirect(); 13 | } 14 | -------------------------------------------------------------------------------- /test_subjects/lib_calls/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "lib_calls" 3 | version = "0.1.0" 4 | authors = [ 5 | "Bart van Schaick ", 6 | "Dominique van Cuilenborg ", 7 | "Fabian Stelmach ", 8 | "Aron Zwaan ", 9 | "Erwin Gribnau " 10 | ] 11 | 12 | [dependencies] 13 | test_subjects_lib = {path = "../../test_subjects_lib/"} 14 | -------------------------------------------------------------------------------- /test_subjects/lib_calls/src/main.rs: -------------------------------------------------------------------------------- 1 | // (C) COPYRIGHT 2018 TECHNOLUTION BV, GOUDA NL 2 | 3 | // Licensed under the Apache License, Version 2.0 or the MIT license 5 | // , at your 6 | // option. This file may not be copied, modified, or distributed 7 | // except according to those terms. 8 | 9 | extern crate test_subjects_lib; 10 | 11 | fn call_box_trait_panics() { 12 | use test_subjects_lib::box_traits::*; 13 | call_panic_box() 14 | } 15 | 16 | fn call_impl_trait_panics() { 17 | use test_subjects_lib::impl_traits::*; 18 | call_impl_trait_panic() 19 | } 20 | 21 | fn call_lambda_struct_panics() { 22 | use test_subjects_lib::lambda_structs::*; 23 | lambda_call(); 24 | lambda_struct_as_trait_call(); 25 | } 26 | 27 | fn call_panic_types() { 28 | use test_subjects_lib::panic_types::*; 29 | standard_panic(); 30 | integer_overflow(); 31 | index_out_of_bounds(); 32 | unwrap_none(); 33 | } 34 | 35 | fn call_reference_calls() { 36 | use test_subjects_lib::reference_calls::*; 37 | unsized_call_trait(); 38 | panic_lambda(); 39 | panic_lambda_local(); 40 | } 41 | 42 | fn call_struct_calls() { 43 | use test_subjects_lib::struct_calls::*; 44 | struct_call(); 45 | struct_call_with_self(); 46 | } 47 | 48 | fn call_trait_dynamic_calls() { 49 | use test_subjects_lib::trait_dynamic_calls::*; 50 | dynamic_call(); 51 | dynamic_call_2(); 52 | reference_call(); 53 | } 54 | 55 | fn call_trait_similar_names() { 56 | use test_subjects_lib::trait_similar_names::*; 57 | trait_call_dup1(); 58 | trait_call_dup_with_self(); 59 | duplicate_trait_call(); 60 | trait_call_dup2(); 61 | trait_call_dup_with_self2(); 62 | duplicate_trait_call2(); 63 | } 64 | 65 | fn call_trait_simple_calls() { 66 | use test_subjects_lib::trait_simple_calls::*; 67 | simple_call(); 68 | simple_call2(); 69 | simple_call3(); 70 | simple_call4(); 71 | } 72 | 73 | fn call_same_vtable() { 74 | use test_subjects_lib::same_vtable::*; 75 | 76 | // False positive 77 | maybe_panic(); 78 | } 79 | 80 | fn main() { 81 | call_box_trait_panics(); 82 | call_impl_trait_panics(); 83 | call_lambda_struct_panics(); 84 | call_panic_types(); 85 | call_reference_calls(); 86 | call_struct_calls(); 87 | call_trait_dynamic_calls(); 88 | call_trait_similar_names(); 89 | call_trait_simple_calls(); 90 | call_same_vtable(); 91 | } 92 | -------------------------------------------------------------------------------- /test_subjects/multi_dep/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "multi_dep" 3 | version = "0.1.0" 4 | authors = [ 5 | "Bart van Schaick ", 6 | "Dominique van Cuilenborg ", 7 | "Fabian Stelmach ", 8 | "Aron Zwaan ", 9 | "Erwin Gribnau " 10 | ] 11 | 12 | [dependencies] 13 | dep = { path = "../dep" } 14 | 15 | simplelog = "0.5.2" 16 | log = "*" 17 | -------------------------------------------------------------------------------- /test_subjects/multi_dep/src/main.rs: -------------------------------------------------------------------------------- 1 | // (C) COPYRIGHT 2018 TECHNOLUTION BV, GOUDA NL 2 | 3 | // Licensed under the Apache License, Version 2.0 or the MIT license 5 | // , at your 6 | // option. This file may not be copied, modified, or distributed 7 | // except according to those terms. 8 | 9 | extern crate dep; 10 | extern crate log; 11 | extern crate simplelog as d1; 12 | 13 | /// When we build this test subject, we have a binary that uses 2 different versions of simplelog 14 | /// (0.5.2 as direct dependency, and 0.4.0 as indirect dependency provided by dep. 15 | /// This is used to test function whitelisting on different versions in /panic_analysis/test/function_whitelists.rs 16 | #[inline(never)] 17 | fn main() { 18 | bar(); 19 | } 20 | 21 | fn bar() { 22 | baz(); 23 | dep::baz() 24 | } 25 | 26 | #[inline(never)] 27 | fn baz() { 28 | use d1::*; 29 | use log::*; 30 | 31 | let logger = TermLogger::new(LevelFilter::Info, Config::default()); 32 | dep::baz(); 33 | } 34 | -------------------------------------------------------------------------------- /test_subjects/trait_invocation/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "trait_invocation" 3 | version = "0.1.0" 4 | authors = [ 5 | "Bart van Schaick ", 6 | "Dominique van Cuilenborg ", 7 | "Fabian Stelmach ", 8 | "Aron Zwaan ", 9 | "Erwin Gribnau " 10 | ] 11 | 12 | [dependencies] 13 | -------------------------------------------------------------------------------- /test_subjects/trait_invocation/src/main.rs: -------------------------------------------------------------------------------- 1 | // (C) COPYRIGHT 2018 TECHNOLUTION BV, GOUDA NL 2 | 3 | // Licensed under the Apache License, Version 2.0 or the MIT license 5 | // , at your 6 | // option. This file may not be copied, modified, or distributed 7 | // except according to those terms. 8 | 9 | struct Foo; 10 | 11 | trait Baz { 12 | fn m5(&self); 13 | fn m6(&self); 14 | } 15 | 16 | impl Baz for Foo { 17 | fn m5(&self) { 18 | println!("Hello! m5"); 19 | } 20 | fn m6(&self) { 21 | println!("Hello! m6"); 22 | } 23 | } 24 | 25 | fn qux(x: &T) { 26 | x.m5(); 27 | x.m6(); 28 | } 29 | 30 | fn main() { 31 | let x: &Baz = &Foo; 32 | qux(x); 33 | } 34 | -------------------------------------------------------------------------------- /test_subjects/unwrap/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "unwrap" 3 | version = "0.1.0" 4 | authors = [ 5 | "Bart van Schaick ", 6 | "Dominique van Cuilenborg ", 7 | "Fabian Stelmach ", 8 | "Aron Zwaan ", 9 | "Erwin Gribnau " 10 | ] 11 | 12 | [dependencies] 13 | test_subjects_lib = {path = "../../test_subjects_lib/"} 14 | -------------------------------------------------------------------------------- /test_subjects/unwrap/src/main.rs: -------------------------------------------------------------------------------- 1 | // (C) COPYRIGHT 2018 TECHNOLUTION BV, GOUDA NL 2 | 3 | // Licensed under the Apache License, Version 2.0 or the MIT license 5 | // , at your 6 | // option. This file may not be copied, modified, or distributed 7 | // except according to those terms. 8 | 9 | extern crate test_subjects_lib; 10 | 11 | /// This test subject builds to en executable that calls `unwrap` and expect in several different ways. 12 | /// It is used in the /panic_analysis/tests/recognize_unwrap.rs integration tests 13 | fn main() { 14 | call_unwrap(); 15 | call_expect(); 16 | call_option_expect(); 17 | call_unwrap_deep(); 18 | panic_otherwise(); 19 | } 20 | 21 | fn call_unwrap() { 22 | test_subjects_lib::unwrap_calls::call_unwrap(); 23 | } 24 | 25 | fn call_expect() { 26 | test_subjects_lib::unwrap_calls::call_expect(); 27 | } 28 | 29 | fn call_option_expect() { 30 | test_subjects_lib::unwrap_calls::call_option_expect(); 31 | } 32 | 33 | fn call_unwrap_deep() { 34 | test_subjects_lib::unwrap_calls::call_unwrap_deep(); 35 | } 36 | 37 | fn panic_otherwise() { 38 | panic!("Custom panic") 39 | } 40 | -------------------------------------------------------------------------------- /test_subjects_lib/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "test_subjects_lib" 3 | version = "0.1.0" 4 | authors = [ 5 | "Bart van Schaick ", 6 | "Dominique van Cuilenborg ", 7 | "Fabian Stelmach ", 8 | "Aron Zwaan ", 9 | "Erwin Gribnau " 10 | ] 11 | 12 | [dependencies] 13 | -------------------------------------------------------------------------------- /test_subjects_lib/src/arithmetic.rs: -------------------------------------------------------------------------------- 1 | // (C) COPYRIGHT 2018 TECHNOLUTION BV, GOUDA NL 2 | 3 | // Licensed under the Apache License, Version 2.0 or the MIT license 5 | // , at your 6 | // option. This file may not be copied, modified, or distributed 7 | // except according to those terms. 8 | 9 | #[inline(never)] 10 | pub fn add(a: i32, b: i32) -> i32 { 11 | a + b 12 | } 13 | 14 | #[inline(never)] 15 | pub fn subtract(a: i32, b: i32) -> i32 { 16 | a - b 17 | } 18 | 19 | #[inline(never)] 20 | pub fn multiply(a: i32, b: i32) -> i32 { 21 | a * b 22 | } 23 | 24 | #[inline(never)] 25 | pub fn divide(a: i32, b: i32) -> i32 { 26 | a / b 27 | } 28 | 29 | #[inline(never)] 30 | pub fn divide_no_overflow(a: i32, b: i32) -> Option { 31 | // If opt-level >= 1, this optimizes away the divide by zero overflow check 32 | if a == ::min_value() { 33 | None 34 | } else { 35 | Some (a / b) 36 | } 37 | } 38 | 39 | #[inline(never)] 40 | pub fn divide_safe(a: i32, b: i32) -> Option { 41 | // If opt-level >= 1, this optimizes away the divide by zero overflow check 42 | if a == ::min_value() || b == 0 { 43 | None 44 | } else { 45 | Some (a / b) 46 | } 47 | } 48 | 49 | #[inline(never)] 50 | pub fn remainder(a: i32, b: i32) -> i32 { 51 | a % b 52 | } 53 | 54 | #[inline(never)] 55 | pub fn remainder_no_overflow(a: i32, b: i32) -> Option { 56 | // If opt-level >= 1, this optimizes away the divide by zero overflow check 57 | if a == ::min_value() { 58 | None 59 | } else { 60 | Some (a % b) 61 | } 62 | } 63 | 64 | #[inline(never)] 65 | pub fn remainder_safe(a: i32, b: i32) -> Option { 66 | // If opt-level >= 1, this optimizes away the divide by zero overflow check 67 | if a == ::min_value() || b == 0{ 68 | None 69 | } else { 70 | Some (a % b) 71 | } 72 | } 73 | 74 | #[inline(never)] 75 | pub fn shl(a: i32, b: i32) -> i32 { 76 | a << b 77 | } 78 | 79 | #[inline(never)] 80 | pub fn shr(a: i32, b: i32) -> i32 { 81 | a >> b 82 | } 83 | 84 | #[inline(never)] 85 | pub fn equal(a: i32, b: i32) { 86 | assert_eq!(a, b); 87 | } -------------------------------------------------------------------------------- /test_subjects_lib/src/box_traits.rs: -------------------------------------------------------------------------------- 1 | // (C) COPYRIGHT 2018 TECHNOLUTION BV, GOUDA NL 2 | 3 | // Licensed under the Apache License, Version 2.0 or the MIT license 5 | // , at your 6 | // option. This file may not be copied, modified, or distributed 7 | // except according to those terms. 8 | 9 | struct Calls; 10 | 11 | trait TraitCalls { 12 | #[inline(never)] 13 | fn trait_call_with_self(&self); 14 | } 15 | 16 | impl TraitCalls for Calls { 17 | #[inline(never)] 18 | fn trait_call_with_self(&self) { 19 | panic!() 20 | } 21 | } 22 | 23 | #[inline(never)] 24 | fn ret_trait_box() -> Box { 25 | Box::new(Calls {}) 26 | } 27 | 28 | #[inline(never)] 29 | pub fn call_panic_box() { 30 | let returned_box_trait = ret_trait_box(); 31 | 32 | returned_box_trait.trait_call_with_self(); 33 | } 34 | -------------------------------------------------------------------------------- /test_subjects_lib/src/impl_traits.rs: -------------------------------------------------------------------------------- 1 | // (C) COPYRIGHT 2018 TECHNOLUTION BV, GOUDA NL 2 | 3 | // Licensed under the Apache License, Version 2.0 or the MIT license 5 | // , at your 6 | // option. This file may not be copied, modified, or distributed 7 | // except according to those terms. 8 | 9 | struct Calls; 10 | 11 | trait TraitCalls { 12 | #[inline(never)] 13 | fn trait_call_with_self(&self); 14 | } 15 | 16 | impl TraitCalls for Calls { 17 | #[inline(never)] 18 | fn trait_call_with_self(&self) { 19 | panic!() 20 | } 21 | } 22 | 23 | #[inline(never)] 24 | fn ret_trait() -> impl TraitCalls { 25 | Calls {} 26 | } 27 | 28 | #[inline(never)] 29 | pub fn call_impl_trait_panic() { 30 | let returned_trait = ret_trait(); 31 | 32 | returned_trait.trait_call_with_self(); 33 | } 34 | -------------------------------------------------------------------------------- /test_subjects_lib/src/inline.rs: -------------------------------------------------------------------------------- 1 | // (C) COPYRIGHT 2018 TECHNOLUTION BV, GOUDA NL 2 | 3 | // Licensed under the Apache License, Version 2.0 or the MIT license 5 | // , at your 6 | // option. This file may not be copied, modified, or distributed 7 | // except according to those terms. 8 | 9 | #[inline(always)] 10 | pub fn call_panic_indirect() { 11 | call_panic_direct(); 12 | } 13 | 14 | #[inline(always)] 15 | fn call_panic_direct() { 16 | panic!("Inlined panic") 17 | } -------------------------------------------------------------------------------- /test_subjects_lib/src/lambda_structs.rs: -------------------------------------------------------------------------------- 1 | // (C) COPYRIGHT 2018 TECHNOLUTION BV, GOUDA NL 2 | 3 | // Licensed under the Apache License, Version 2.0 or the MIT license 5 | // , at your 6 | // option. This file may not be copied, modified, or distributed 7 | // except according to those terms. 8 | 9 | struct CallsWithFn<'a, T: 'a> 10 | where 11 | T: Fn(), 12 | { 13 | lambda: &'a T, 14 | } 15 | 16 | trait TraitCallsWithFn { 17 | #[inline(never)] 18 | fn call_inner_lambda(&self); 19 | } 20 | 21 | impl<'a, T> TraitCallsWithFn for CallsWithFn<'a, T> 22 | where 23 | T: Fn(), 24 | { 25 | #[inline(never)] 26 | fn call_inner_lambda(&self) { 27 | let lambda = &self.lambda; 28 | lambda(); 29 | } 30 | } 31 | 32 | #[inline(never)] 33 | fn ret_lambda() -> fn() { 34 | || panic!() 35 | } 36 | 37 | #[inline(never)] 38 | pub fn lambda_call() { 39 | let panic_lambda_struct = CallsWithFn { 40 | lambda: &ret_lambda(), 41 | }; 42 | panic_lambda_struct.call_inner_lambda(); 43 | } 44 | 45 | /// This is a difficult construction: 46 | /// we get a box which contains a trait which calls a function that calls a generic reference to an anonymous function 47 | #[inline(never)] 48 | pub fn lambda_struct_as_trait_call() { 49 | let lambda = ret_lambda(); 50 | let panic_lambda_struct_as_trait: Box = 51 | Box::new(CallsWithFn { lambda: &lambda }); 52 | panic_lambda_struct_as_trait.call_inner_lambda(); 53 | } -------------------------------------------------------------------------------- /test_subjects_lib/src/lib.rs: -------------------------------------------------------------------------------- 1 | // (C) COPYRIGHT 2018 TECHNOLUTION BV, GOUDA NL 2 | 3 | // Licensed under the Apache License, Version 2.0 or the MIT license 5 | // , at your 6 | // option. This file may not be copied, modified, or distributed 7 | // except according to those terms. 8 | 9 | pub mod box_traits; 10 | pub mod impl_traits; 11 | pub mod lambda_structs; 12 | pub mod panic_types; 13 | pub mod reference_calls; 14 | pub mod struct_calls; 15 | pub mod trait_dynamic_calls; 16 | pub mod trait_similar_names; 17 | pub mod trait_simple_calls; 18 | pub mod same_vtable; 19 | pub mod unwrap_calls; 20 | pub mod inline; 21 | pub mod arithmetic; -------------------------------------------------------------------------------- /test_subjects_lib/src/panic_types.rs: -------------------------------------------------------------------------------- 1 | // (C) COPYRIGHT 2018 TECHNOLUTION BV, GOUDA NL 2 | 3 | // Licensed under the Apache License, Version 2.0 or the MIT license 5 | // , at your 6 | // option. This file may not be copied, modified, or distributed 7 | // except according to those terms. 8 | 9 | pub fn standard_panic() { 10 | panic!(); 11 | } 12 | 13 | pub fn integer_overflow() { 14 | let possible_overflow = 1 + 1; 15 | } 16 | 17 | pub fn index_out_of_bounds() { 18 | let v = vec![0, 1, 2]; 19 | let oob = v[3]; 20 | } 21 | 22 | pub fn unwrap_none() { 23 | let option: Option = None; 24 | option.unwrap(); 25 | } 26 | -------------------------------------------------------------------------------- /test_subjects_lib/src/reference_calls.rs: -------------------------------------------------------------------------------- 1 | // (C) COPYRIGHT 2018 TECHNOLUTION BV, GOUDA NL 2 | 3 | // Licensed under the Apache License, Version 2.0 or the MIT license 5 | // , at your 6 | // option. This file may not be copied, modified, or distributed 7 | // except according to those terms. 8 | 9 | struct Calls; 10 | 11 | trait TraitCalls { 12 | #[inline(never)] 13 | fn trait_call_with_self(&self); 14 | } 15 | 16 | impl TraitCalls for Calls { 17 | #[inline(never)] 18 | fn trait_call_with_self(&self) { 19 | panic!() 20 | } 21 | } 22 | 23 | #[inline(never)] 24 | fn ret_lambda() -> fn() { 25 | || panic!() 26 | } 27 | 28 | #[inline(never)] 29 | fn call_unsized(call: &T) { 30 | call.trait_call_with_self(); 31 | } 32 | 33 | #[inline(never)] 34 | fn call_with_reference(call: T) { 35 | call.trait_call_with_self(); 36 | } 37 | 38 | #[inline(never)] 39 | pub fn reference_call() { 40 | call_with_reference(Calls); 41 | } 42 | 43 | #[inline(never)] 44 | pub fn unsized_call() { 45 | call_unsized(&Calls); 46 | } 47 | 48 | #[inline(never)] 49 | pub fn unsized_call_trait() { 50 | let calls: &TraitCalls = &Calls; 51 | call_unsized(calls); 52 | } 53 | 54 | #[inline(never)] 55 | pub fn panic_lambda() { 56 | let panic_lambda = ret_lambda(); 57 | panic_lambda(); 58 | } 59 | 60 | #[inline(never)] 61 | pub fn panic_lambda_local() { 62 | let panic_lambda_2 = || panic!(); 63 | panic_lambda_2(); 64 | } 65 | -------------------------------------------------------------------------------- /test_subjects_lib/src/same_vtable.rs: -------------------------------------------------------------------------------- 1 | // (C) COPYRIGHT 2018 TECHNOLUTION BV, GOUDA NL 2 | 3 | // Licensed under the Apache License, Version 2.0 or the MIT license 5 | // , at your 6 | // option. This file may not be copied, modified, or distributed 7 | // except according to those terms. 8 | 9 | trait Call { 10 | #[inline(never)] 11 | fn maybe_panic(&self); 12 | 13 | #[inline(never)] 14 | fn maybe_panic2(&self); 15 | } 16 | 17 | struct S1; 18 | struct S2 { 19 | s1: S1, 20 | } 21 | 22 | impl Call for S1 { 23 | #[inline(never)] 24 | fn maybe_panic(&self) { } 25 | 26 | fn maybe_panic2(&self) { 27 | panic!() 28 | } 29 | } 30 | 31 | /// Should not panic 32 | #[inline(never)] 33 | pub fn maybe_panic() { 34 | let s1 = S1{}; 35 | let s = &s1 as &Call; 36 | s.maybe_panic2(); 37 | } 38 | 39 | /// Should panic 40 | #[inline(never)] 41 | pub fn maybe_panic2() { 42 | let s1 = S1{}; 43 | let s = &s1 as &Call; 44 | s.maybe_panic(); 45 | } 46 | -------------------------------------------------------------------------------- /test_subjects_lib/src/struct_calls.rs: -------------------------------------------------------------------------------- 1 | // (C) COPYRIGHT 2018 TECHNOLUTION BV, GOUDA NL 2 | 3 | // Licensed under the Apache License, Version 2.0 or the MIT license 5 | // , at your 6 | // option. This file may not be copied, modified, or distributed 7 | // except according to those terms. 8 | 9 | struct StructCalls; 10 | 11 | impl StructCalls { 12 | #[inline(never)] 13 | fn struct_call() { 14 | panic!(); 15 | } 16 | 17 | #[inline(never)] 18 | fn struct_call_with_self(&self) { 19 | panic!(); 20 | } 21 | } 22 | 23 | #[inline(never)] 24 | pub fn struct_call() { 25 | StructCalls::struct_call(); 26 | } 27 | 28 | #[inline(never)] 29 | pub fn struct_call_with_self() { 30 | StructCalls::struct_call_with_self(&StructCalls); 31 | } 32 | -------------------------------------------------------------------------------- /test_subjects_lib/src/trait_dynamic_calls.rs: -------------------------------------------------------------------------------- 1 | // (C) COPYRIGHT 2018 TECHNOLUTION BV, GOUDA NL 2 | 3 | // Licensed under the Apache License, Version 2.0 or the MIT license 5 | // , at your 6 | // option. This file may not be copied, modified, or distributed 7 | // except according to those terms. 8 | 9 | struct Calls; 10 | 11 | trait TraitCallsWithReceiver { 12 | #[inline(never)] 13 | fn call_with_self(&self); 14 | } 15 | 16 | trait TraitCallsWithReceiverDup { 17 | #[inline(never)] 18 | fn call_with_self(&self); 19 | } 20 | 21 | impl TraitCallsWithReceiver for Calls { 22 | #[inline(never)] 23 | fn call_with_self(&self) { 24 | panic!(); 25 | } 26 | } 27 | 28 | impl TraitCallsWithReceiverDup for Calls { 29 | #[inline(never)] 30 | fn call_with_self(&self) { 31 | panic!(); 32 | } 33 | } 34 | 35 | impl Calls { 36 | #[inline(never)] 37 | fn call_with_self(&self) { 38 | panic!(); 39 | } 40 | } 41 | 42 | #[inline(never)] 43 | pub fn dynamic_call() { 44 | let call: &TraitCallsWithReceiver = &Calls; 45 | call.call_with_self(); 46 | } 47 | 48 | #[inline(never)] 49 | pub fn dynamic_call_2() { 50 | let call: &TraitCallsWithReceiver = &Calls; 51 | TraitCallsWithReceiver::call_with_self(call); 52 | } 53 | 54 | #[inline(never)] 55 | pub fn reference_call() { 56 | let call = &Calls; 57 | call.call_with_self(); 58 | } 59 | -------------------------------------------------------------------------------- /test_subjects_lib/src/trait_similar_names.rs: -------------------------------------------------------------------------------- 1 | // (C) COPYRIGHT 2018 TECHNOLUTION BV, GOUDA NL 2 | 3 | // Licensed under the Apache License, Version 2.0 or the MIT license 5 | // , at your 6 | // option. This file may not be copied, modified, or distributed 7 | // except according to those terms. 8 | 9 | struct Calls; 10 | 11 | trait SimilarTraitCalls { 12 | #[inline(never)] 13 | fn trait_call_dup(); 14 | #[inline(never)] 15 | fn trait_call_with_self_dup(&self); 16 | } 17 | 18 | trait SimilarTraitCalls2 { 19 | #[inline(never)] 20 | fn trait_call_dup(); 21 | #[inline(never)] 22 | fn trait_call_with_self_dup(&self); 23 | } 24 | 25 | impl SimilarTraitCalls for Calls { 26 | #[inline(never)] 27 | fn trait_call_dup() { 28 | } 29 | #[inline(never)] 30 | fn trait_call_with_self_dup(&self) { 31 | } 32 | } 33 | 34 | impl SimilarTraitCalls2 for Calls { 35 | #[inline(never)] 36 | fn trait_call_dup() { 37 | panic!(); 38 | } 39 | #[inline(never)] 40 | fn trait_call_with_self_dup(&self) { 41 | panic!(); 42 | } 43 | } 44 | 45 | #[inline(never)] 46 | pub fn trait_call_dup1() { 47 | ::trait_call_dup(); 48 | } 49 | 50 | #[inline(never)] 51 | pub fn trait_call_dup_with_self() { 52 | ::trait_call_with_self_dup(&Calls); 53 | } 54 | 55 | #[inline(never)] 56 | pub fn duplicate_trait_call() { 57 | SimilarTraitCalls::trait_call_with_self_dup(&Calls); 58 | } 59 | 60 | #[inline(never)] 61 | pub fn trait_call_dup2() { 62 | ::trait_call_dup(); 63 | } 64 | 65 | #[inline(never)] 66 | pub fn trait_call_dup_with_self2() { 67 | ::trait_call_with_self_dup(&Calls); 68 | } 69 | 70 | #[inline(never)] 71 | pub fn duplicate_trait_call2() { 72 | SimilarTraitCalls2::trait_call_with_self_dup(&Calls); 73 | } 74 | -------------------------------------------------------------------------------- /test_subjects_lib/src/trait_simple_calls.rs: -------------------------------------------------------------------------------- 1 | // (C) COPYRIGHT 2018 TECHNOLUTION BV, GOUDA NL 2 | 3 | // Licensed under the Apache License, Version 2.0 or the MIT license 5 | // , at your 6 | // option. This file may not be copied, modified, or distributed 7 | // except according to those terms. 8 | 9 | struct Calls; 10 | 11 | trait TraitCalls { 12 | #[inline(never)] 13 | fn trait_call(); 14 | #[inline(never)] 15 | fn trait_call_with_self(&self); 16 | } 17 | 18 | impl TraitCalls for Calls { 19 | #[inline(never)] 20 | fn trait_call() { 21 | panic!(); 22 | } 23 | #[inline(never)] 24 | fn trait_call_with_self(&self) { 25 | panic!(); 26 | } 27 | } 28 | 29 | #[inline(never)] 30 | pub fn simple_call() { 31 | Calls::trait_call(); 32 | } 33 | 34 | #[inline(never)] 35 | pub fn simple_call2() { 36 | Calls::trait_call_with_self(&Calls); 37 | } 38 | 39 | #[inline(never)] 40 | pub fn simple_call3() { 41 | Calls.trait_call_with_self(); 42 | } 43 | 44 | #[inline(never)] 45 | pub fn simple_call4() { 46 | ::trait_call(); 47 | } -------------------------------------------------------------------------------- /test_subjects_lib/src/unwrap_calls.rs: -------------------------------------------------------------------------------- 1 | // (C) COPYRIGHT 2018 TECHNOLUTION BV, GOUDA NL 2 | 3 | // Licensed under the Apache License, Version 2.0 or the MIT license 5 | // , at your 6 | // option. This file may not be copied, modified, or distributed 7 | // except according to those terms. 8 | 9 | pub fn call_unwrap() { 10 | let option: Option<()> = None; 11 | option.unwrap(); 12 | } 13 | 14 | pub fn call_expect() { 15 | let option: Result<(), ()> = Err(()); 16 | option.expect("No value given"); 17 | } 18 | 19 | pub fn call_option_expect() { 20 | let option: Option<()> = None; 21 | option.expect("Custom error message for expect call on an option"); 22 | } 23 | 24 | pub fn call_unwrap_deep() { 25 | call_unwrap_deep_2() 26 | } 27 | 28 | fn call_unwrap_deep_2() { 29 | call_unwrap_deep_3() 30 | } 31 | 32 | fn call_unwrap_deep_3() { 33 | let option: Option<()> = None; 34 | option.unwrap(); 35 | } -------------------------------------------------------------------------------- /test_subjects_stable_rustc/.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | /target 3 | **/*.rs.bk 4 | -------------------------------------------------------------------------------- /test_subjects_stable_rustc/Cargo.lock: -------------------------------------------------------------------------------- 1 | [[package]] 2 | name = "hello_world" 3 | version = "0.1.0" 4 | 5 | [[package]] 6 | name = "threads" 7 | version = "0.1.0" 8 | 9 | -------------------------------------------------------------------------------- /test_subjects_stable_rustc/Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | 3 | members = [ 4 | "hello_world", 5 | "threads", 6 | ] 7 | 8 | [profile.release] 9 | debug = true 10 | -------------------------------------------------------------------------------- /test_subjects_stable_rustc/README.md: -------------------------------------------------------------------------------- 1 | # Test subjects with stable rustc 2 | This project is a workspace that contains multiple projects used for testing of the `rustig` crate. These projects should be compiled with a specific `rustc` version: `stable-2018-05-10`. The build step responsible for enforcing this can be found in: `rustig/lib/callgraph/build.rs` 3 | 4 | 5 | ## Adding new projects 6 | In order to add a new project, use the command: `cargo new --bin $projectname$`, where `$projectname$` is the name of the new test subject. 7 | 8 | Additionally, the project needs to be added to the `Cargo.toml` file. In order to do this, extend the `Cargo.toml` file by adding a new value to the `members` field, with the value of `"$projectname$"`. 9 | 10 | Note that adding, removing or changing a test subject requires a clean build to work properly. 11 | -------------------------------------------------------------------------------- /test_subjects_stable_rustc/hello_world/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "hello_world" 3 | version = "0.1.0" 4 | authors = ["..."] 5 | 6 | [dependencies] 7 | -------------------------------------------------------------------------------- /test_subjects_stable_rustc/hello_world/src/main.rs: -------------------------------------------------------------------------------- 1 | // (C) COPYRIGHT 2018 TECHNOLUTION BV, GOUDA NL 2 | 3 | // Licensed under the Apache License, Version 2.0 or the MIT license 5 | // , at your 6 | // option. This file may not be copied, modified, or distributed 7 | // except according to those terms. 8 | 9 | fn main() { 10 | println!("Hello, world!"); 11 | } 12 | -------------------------------------------------------------------------------- /test_subjects_stable_rustc/threads/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "threads" 3 | version = "0.1.0" 4 | authors = [ 5 | "Bart van Schaick ", 6 | "Dominique van Cuilenborg ", 7 | "Fabian Stelmach ", 8 | "Aron Zwaan ", 9 | "Erwin Gribnau " 10 | ] 11 | 12 | [dependencies] 13 | -------------------------------------------------------------------------------- /test_subjects_stable_rustc/threads/src/main.rs: -------------------------------------------------------------------------------- 1 | // (C) COPYRIGHT 2018 TECHNOLUTION BV, GOUDA NL 2 | 3 | // Licensed under the Apache License, Version 2.0 or the MIT license 5 | // , at your 6 | // option. This file may not be copied, modified, or distributed 7 | // except according to those terms. 8 | 9 | use std::any::Any; 10 | use std::thread; 11 | 12 | fn main() -> Result<(), Box> { 13 | let handle1 = thread::spawn(|| { 14 | println!("Hello from thread 1"); 15 | panic!("Panic in thread 1"); 16 | }); 17 | 18 | let handle2 = thread::spawn(|| { 19 | println!("Hello from thread 2"); 20 | panic!("Panic in thread 2"); 21 | }); 22 | 23 | handle1.join()?; 24 | handle2.join()? 25 | } 26 | --------------------------------------------------------------------------------