├── .gitignore ├── src ├── hooks.rs ├── return_value.rs ├── parameter_val.rs ├── test_utils.rs ├── alloc.rs ├── callbacks.rs ├── hooks │ ├── exceptions.rs │ └── allocation.rs ├── double_keyed_map.rs ├── error.rs ├── alloc_utils.rs └── demangling.rs ├── tests ├── bcfiles │ ├── abort.bc │ ├── basic.bc │ ├── call.bc │ ├── issue_10.rs │ ├── loop.bc │ ├── memory.bc │ ├── panic.bc │ ├── simd.bc │ ├── struct.bc │ ├── crossmod.bc │ ├── globals.bc │ ├── issue_10.bc │ ├── issue_4.bc │ ├── issue_9.bc │ ├── simd_cl.bc │ ├── atomicrmw.bc │ ├── functionptr.bc │ ├── linkedlist.bc │ ├── struct-O3.bc │ ├── throwcatch.bc │ ├── 32bit │ │ ├── issue_4.bc │ │ └── issue_4.ll │ ├── globals_initialization_1.bc │ ├── globals_initialization_2.bc │ ├── panic.rs │ ├── abort.c │ ├── issue_4.rs │ ├── globals_initialization.h │ ├── globals.c │ ├── simd.c │ ├── issue_9.rs │ ├── struct-O3.c │ ├── atomicrmw.ll │ ├── functionptr.c │ ├── simd_cl.cl │ ├── globals_initialization_2.c │ ├── linkedlist.c │ ├── memory.c │ ├── crossmod.c │ ├── globals_initialization_1.c │ ├── loop.c │ ├── abort.ll │ ├── basic.c │ ├── globals_initialization_2.ll │ ├── Makefile │ ├── globals.ll │ ├── throwcatch.cpp │ ├── call.c │ ├── globals_initialization_1.ll │ ├── crossmod.ll │ ├── struct-O3.ll │ ├── struct.c │ ├── simd_cl.ll │ ├── functionptr.ll │ ├── memory.ll │ ├── basic.ll │ ├── simd.ll │ ├── linkedlist.ll │ └── issue_4.ll ├── functionptr_tests.rs ├── linkedlist_tests.rs ├── may_abort_tests.rs ├── rmw_tests.rs ├── hook_tests.rs ├── loop_tests.rs ├── global_tests.rs ├── memory_tests.rs ├── simd_tests.rs ├── throwcatch_tests.rs └── call_tests.rs ├── rustfmt.toml ├── refresh-docs.sh ├── LICENSE └── Cargo.toml /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | **/*~ 4 | **/*.bak 5 | 6 | Cargo.lock 7 | -------------------------------------------------------------------------------- /src/hooks.rs: -------------------------------------------------------------------------------- 1 | pub mod allocation; 2 | pub mod exceptions; 3 | pub mod intrinsics; 4 | -------------------------------------------------------------------------------- /tests/bcfiles/abort.bc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PLSysSec/haybale/HEAD/tests/bcfiles/abort.bc -------------------------------------------------------------------------------- /tests/bcfiles/basic.bc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PLSysSec/haybale/HEAD/tests/bcfiles/basic.bc -------------------------------------------------------------------------------- /tests/bcfiles/call.bc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PLSysSec/haybale/HEAD/tests/bcfiles/call.bc -------------------------------------------------------------------------------- /tests/bcfiles/issue_10.rs: -------------------------------------------------------------------------------- 1 | pub fn panic_if_not_zero(x: u32) { 2 | assert_eq!(x, 0); 3 | } 4 | -------------------------------------------------------------------------------- /tests/bcfiles/loop.bc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PLSysSec/haybale/HEAD/tests/bcfiles/loop.bc -------------------------------------------------------------------------------- /tests/bcfiles/memory.bc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PLSysSec/haybale/HEAD/tests/bcfiles/memory.bc -------------------------------------------------------------------------------- /tests/bcfiles/panic.bc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PLSysSec/haybale/HEAD/tests/bcfiles/panic.bc -------------------------------------------------------------------------------- /tests/bcfiles/simd.bc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PLSysSec/haybale/HEAD/tests/bcfiles/simd.bc -------------------------------------------------------------------------------- /tests/bcfiles/struct.bc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PLSysSec/haybale/HEAD/tests/bcfiles/struct.bc -------------------------------------------------------------------------------- /tests/bcfiles/crossmod.bc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PLSysSec/haybale/HEAD/tests/bcfiles/crossmod.bc -------------------------------------------------------------------------------- /tests/bcfiles/globals.bc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PLSysSec/haybale/HEAD/tests/bcfiles/globals.bc -------------------------------------------------------------------------------- /tests/bcfiles/issue_10.bc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PLSysSec/haybale/HEAD/tests/bcfiles/issue_10.bc -------------------------------------------------------------------------------- /tests/bcfiles/issue_4.bc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PLSysSec/haybale/HEAD/tests/bcfiles/issue_4.bc -------------------------------------------------------------------------------- /tests/bcfiles/issue_9.bc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PLSysSec/haybale/HEAD/tests/bcfiles/issue_9.bc -------------------------------------------------------------------------------- /tests/bcfiles/simd_cl.bc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PLSysSec/haybale/HEAD/tests/bcfiles/simd_cl.bc -------------------------------------------------------------------------------- /tests/bcfiles/atomicrmw.bc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PLSysSec/haybale/HEAD/tests/bcfiles/atomicrmw.bc -------------------------------------------------------------------------------- /tests/bcfiles/functionptr.bc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PLSysSec/haybale/HEAD/tests/bcfiles/functionptr.bc -------------------------------------------------------------------------------- /tests/bcfiles/linkedlist.bc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PLSysSec/haybale/HEAD/tests/bcfiles/linkedlist.bc -------------------------------------------------------------------------------- /tests/bcfiles/struct-O3.bc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PLSysSec/haybale/HEAD/tests/bcfiles/struct-O3.bc -------------------------------------------------------------------------------- /tests/bcfiles/throwcatch.bc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PLSysSec/haybale/HEAD/tests/bcfiles/throwcatch.bc -------------------------------------------------------------------------------- /tests/bcfiles/32bit/issue_4.bc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PLSysSec/haybale/HEAD/tests/bcfiles/32bit/issue_4.bc -------------------------------------------------------------------------------- /tests/bcfiles/globals_initialization_1.bc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PLSysSec/haybale/HEAD/tests/bcfiles/globals_initialization_1.bc -------------------------------------------------------------------------------- /tests/bcfiles/globals_initialization_2.bc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PLSysSec/haybale/HEAD/tests/bcfiles/globals_initialization_2.bc -------------------------------------------------------------------------------- /tests/bcfiles/panic.rs: -------------------------------------------------------------------------------- 1 | pub fn may_panic(a: i32) -> i32 { 2 | if a > 2 { 3 | panic!("a > 2"); 4 | } else { 5 | return 1; 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /tests/bcfiles/abort.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | int may_exit(int a) { 4 | if (a > 2) { 5 | exit(1); 6 | } else { 7 | return 1; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /tests/bcfiles/issue_4.rs: -------------------------------------------------------------------------------- 1 | // Issue #4 2 | 3 | pub fn ez(input: u32) -> u32 { 4 | input * 2 5 | } 6 | 7 | pub fn main() { 8 | let out = ez(1); 9 | println!("out: {}", out); 10 | } 11 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | ignore = [ 2 | "tests/bcfiles", 3 | ] 4 | 5 | unstable_features = true 6 | 7 | imports_layout = "HorizontalVertical" 8 | 9 | match_block_trailing_comma = true 10 | 11 | newline_style = "Unix" 12 | 13 | spaces_around_ranges = true 14 | 15 | use_field_init_shorthand = true 16 | -------------------------------------------------------------------------------- /tests/bcfiles/globals_initialization.h: -------------------------------------------------------------------------------- 1 | struct SomeStruct { 2 | const int field1; 3 | const int field2; 4 | const int field3; 5 | }; 6 | 7 | struct StructWithPointers { 8 | const int field1; 9 | const int* intptr; 10 | const struct SomeStruct* ssptr; 11 | const struct StructWithPointers* swpptr; 12 | }; 13 | 14 | struct StructWithFunctionPointer { 15 | const int field1; 16 | int (*const funcptr)(int, int); 17 | void* voidfuncptr; 18 | }; 19 | 20 | int foo(); 21 | int bar(int, int); 22 | -------------------------------------------------------------------------------- /refresh-docs.sh: -------------------------------------------------------------------------------- 1 | #!/bin/zsh 2 | 3 | # Requires ghp-import to be installed (e.g. via pip3) 4 | 5 | rm -rf target/doc && # purge old docs that may include docs for deps 6 | cargo doc --no-deps --features=llvm-13 && # document just this crate 7 | echo "" > target/doc/index.html && # put in the top-level redirect 8 | ghp-import -np target/doc && # publish to gh-pages branch 9 | rm -rf target/doc && # kill the docs that were just this crate 10 | cargo doc --features=llvm-13 # regenerate all docs (including deps) for local use 11 | -------------------------------------------------------------------------------- /tests/bcfiles/globals.c: -------------------------------------------------------------------------------- 1 | volatile int global1 = 3; 2 | volatile int global2 = 5; 3 | volatile int global3; 4 | 5 | __attribute__((noinline)) int read_global() { 6 | return global1; 7 | } 8 | 9 | __attribute__((noinline)) int modify_global(int x) { 10 | global3 = x; 11 | return global3; 12 | } 13 | 14 | __attribute__((noinline)) int modify_global_with_call(int x) { 15 | modify_global(x); 16 | return global3; 17 | } 18 | 19 | int dont_confuse_globals(int x) { 20 | global1 = 100; 21 | global2 = 95; 22 | global3 = x; 23 | global1 = global2 - 200; 24 | return global3; 25 | } 26 | -------------------------------------------------------------------------------- /tests/bcfiles/simd.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | __attribute__((noinline)) uint32_t callee(uint32_t* x, uint32_t* y, uint32_t*__restrict z) { 5 | for(int i = 0; i < 16; i++) { 6 | x[i] = i; 7 | y[i] = i + 2; 8 | } 9 | for (int i = 0; i < 16; i++) z[i] = x[i] + y[i]; 10 | uint32_t sum = 0; 11 | for (int i = 0; i < 16; i++) sum += z[i]; 12 | return sum; 13 | } 14 | 15 | uint32_t simd_add_autovectorized() { 16 | // actually allocate the arrays so that the symex gives them concrete addresses 17 | uint32_t x[16]; 18 | uint32_t y[16]; 19 | uint32_t z[16]; 20 | return callee(x, y, z); 21 | } 22 | -------------------------------------------------------------------------------- /tests/bcfiles/issue_9.rs: -------------------------------------------------------------------------------- 1 | struct Foo { 2 | a: &'static mut [u32], 3 | } 4 | 5 | impl Foo { 6 | pub fn ez2(&mut self, input: u32) -> &mut [u32] { 7 | if input < 5 { 8 | self.a 9 | } else { 10 | &mut [] 11 | } 12 | } 13 | 14 | pub fn ez3(&mut self, input: u32) -> usize { 15 | if self.ez2(input).len() > 0 { 16 | 1 17 | } else { 18 | panic!("abort"); 19 | } 20 | } 21 | } 22 | 23 | static mut BUF: [u32; 2] = [1, 2]; 24 | 25 | pub fn main() { 26 | let mut foo = unsafe { Foo { a: &mut BUF } }; 27 | let out = foo.ez3(2); 28 | println!("out: {}", out); 29 | } 30 | -------------------------------------------------------------------------------- /src/return_value.rs: -------------------------------------------------------------------------------- 1 | /// A simple enum describing the value returned from a function 2 | #[derive(PartialEq, Eq, Clone, Copy, Debug, Hash)] 3 | pub enum ReturnValue { 4 | /// The function or call returns this value 5 | Return(V), 6 | /// The function or call returns void 7 | ReturnVoid, 8 | /// The function or call throws this value (using the LLVM `invoke`/`resume` 9 | /// mechanism, which is used for e.g. C++ exceptions) 10 | /// 11 | /// (note that, unless other comments say otherwise, this is a pointer to the 12 | /// actual value or object thrown, not the value itself) 13 | Throw(V), 14 | /// The function or call aborts without ever returning (e.g., with a Rust 15 | /// panic, or by calling the C `exit()` function) 16 | Abort, 17 | } 18 | -------------------------------------------------------------------------------- /tests/bcfiles/struct-O3.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | struct TwoInts { 4 | int el1; 5 | int el2; 6 | }; 7 | 8 | struct WithPointer { 9 | volatile struct TwoInts ti; 10 | volatile struct TwoInts* volatile* ti_2; 11 | }; 12 | 13 | // function that takes a pointer to a struct as an argument 14 | // really the point here is to get a GEP with multiple indices on -O3 15 | __attribute__((noinline)) 16 | int called(struct WithPointer* wp, int x) { 17 | wp->ti.el2 = x - 3; 18 | return wp->ti_2[0]->el2; 19 | } 20 | 21 | int with_ptr(int x) { 22 | volatile struct TwoInts* ti = (volatile struct TwoInts*) malloc(sizeof(struct TwoInts)); 23 | struct WithPointer* wp = (struct WithPointer*) malloc(sizeof(struct WithPointer)); 24 | if (wp == NULL || ti == NULL) { 25 | return -1; 26 | } 27 | wp->ti.el2 = 0; 28 | wp->ti_2 = &ti; 29 | wp->ti_2[0] = &wp->ti; 30 | return called(wp, x); 31 | } 32 | -------------------------------------------------------------------------------- /src/parameter_val.rs: -------------------------------------------------------------------------------- 1 | #[derive(Clone, PartialEq, Eq, Debug)] 2 | pub enum ParameterVal { 3 | /// The parameter can have any value whatsoever. (The analysis will 4 | /// effectively consider all possible values.) 5 | Unconstrained, 6 | /// The parameter will have this exact value. 7 | ExactValue(u64), 8 | /// The parameter can have any value in this range (inclusive). 9 | Range(u64, u64), 10 | /// The parameter will have a non-null value, but otherwise be completely 11 | /// unconstrained (could point anywhere or alias anything). 12 | /// This can only be used for pointer-type parameters. 13 | NonNullPointer, 14 | /// The parameter will point to allocated memory, with the given allocation 15 | /// size in bytes. It will not be NULL and will not alias any other allocated 16 | /// memory. 17 | /// This can only be used for pointer-type parameters. 18 | PointerToAllocated(u64), 19 | } 20 | 21 | impl Default for ParameterVal { 22 | fn default() -> Self { 23 | Self::Unconstrained 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /tests/bcfiles/atomicrmw.ll: -------------------------------------------------------------------------------- 1 | source_filename = "" 2 | target datalayout = "e-m:o-i64:64-f80:128-n8:16:32:64-S128" 3 | target triple = "x86_64-apple-macosx10.15.0" 4 | 5 | define i32 @atomicrmwops(i32, i32) local_unnamed_addr { 6 | %addr = alloca i32, align 4 7 | 8 | ; initial value at %addr is %0 9 | store i32 %0, i32* %addr, align 4 10 | 11 | %3 = atomicrmw xchg i32* %addr, i32 %1 monotonic 12 | ; now %3 is %0 and the value is %1 13 | 14 | %4 = atomicrmw add i32* %addr, i32 %3 acquire 15 | ; now %4 is %1 and the value is %1 + %3 (so, %1 + %0) 16 | 17 | %5 = atomicrmw sub i32* %addr, i32 %1 release 18 | ; now %5 is %1 + %0 and the value is %0 19 | 20 | %6 = atomicrmw and i32* %addr, i32 %4 acq_rel 21 | ; now %6 is %0 and the value is %0 & %1 22 | 23 | %7 = atomicrmw xor i32* %addr, i32 3 seq_cst 24 | ; now %7 is %0 & %1 and the value is (%0 & %1) ^ 3 25 | 26 | %8 = atomicrmw umax i32* %addr, i32 %0 monotonic 27 | ; now %8 is (%0 & %1) ^ 3 and the value is umax(%8, %0) 28 | 29 | %9 = load i32, i32* %addr, align 4 30 | 31 | ret i32 %9 32 | } 33 | -------------------------------------------------------------------------------- /tests/bcfiles/functionptr.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | __attribute__((noinline)) int foo(int x, int y) { 4 | return x * (y + 3); 5 | } 6 | 7 | __attribute__((noinline)) int bar(int x, int y) { 8 | return x - y; 9 | } 10 | 11 | typedef int (*footype)(int, int); 12 | 13 | __attribute__((noinline)) int calls_fptr(volatile footype fptr, int z) { 14 | return fptr(2, 3) + z; 15 | } 16 | 17 | __attribute__((noinline)) footype get_function_ptr(bool b) { 18 | if (b) { 19 | return &foo; 20 | } else { 21 | return &bar; 22 | } 23 | } 24 | 25 | int fptr_driver() { 26 | int (*volatile fptr)(int, int) = get_function_ptr(true); 27 | return calls_fptr(fptr, 10); 28 | } 29 | 30 | struct StructWithFuncPtr { 31 | int anInt; 32 | int (*fptr)(int, int); 33 | }; 34 | 35 | __attribute((noinline)) int calls_through_struct(volatile struct StructWithFuncPtr *s) { 36 | return s->fptr(s->anInt, 2); 37 | } 38 | 39 | int struct_driver() { 40 | volatile struct StructWithFuncPtr s = { 0 }; 41 | s.fptr = get_function_ptr(true); 42 | s.anInt = 3; 43 | return calls_through_struct(&s); 44 | } 45 | -------------------------------------------------------------------------------- /tests/bcfiles/simd_cl.cl: -------------------------------------------------------------------------------- 1 | // This file uses OpenCL to more easily generate LLVM first-class vector operations 2 | 3 | uint simd_add(uint x, uint y) { 4 | uint4 a = (uint4)(x); 5 | uint4 b = { y, y + 1, y + 2, y + 3 }; 6 | uint4 c = a + b; 7 | return c.x + c.y + c.z + c.w; 8 | } 9 | 10 | uint simd_ops(uint x, uint y) { 11 | uint4 a = (uint4)(x); 12 | uint4 b = { y, y + 1, y + 2, y + 3 }; 13 | uint4 c = a + b - (uint4)(3); 14 | uint4 d = c * 17; 15 | uint4 e = (d & (~a)) | b; 16 | uint4 f = e >> 2; 17 | uint4 g = f << (uint4)(2,3,4,5); 18 | return g.x + g.y + g.z + g.w; 19 | } 20 | 21 | uint simd_select(uint x, uint y) { 22 | uint4 a = (uint4)(x); 23 | uint4 b = { y, y + 1, y + 2, y + 3 }; 24 | uint4 c = a < b ? a : b; 25 | return c.x + c.y + c.z + c.w; 26 | } 27 | 28 | uint simd_typeconversions(uint x, uint y) { 29 | uint4 a = (uint4)(x); 30 | uint4 b = { y, y + 10, y + 3, y + 30 }; 31 | ulong4 c = { a.x, a.y, a.z, a.w }; 32 | ulong4 d = { b.x, b.y, b.z, b.w }; 33 | ulong4 e = d - c; 34 | uint4 f = { e.x, e.y, e.z, e.w }; 35 | return f.x + f.y + f.z + f.w; 36 | } 37 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Craig Disselkoen 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 | -------------------------------------------------------------------------------- /tests/bcfiles/globals_initialization_2.c: -------------------------------------------------------------------------------- 1 | // More complicated global-variable initialization, including referring to global variables in other modules 2 | 3 | #include "globals_initialization.h" 4 | 5 | // forward declarations (these are defined in globals_initialization_1.c) 6 | extern const int a, b, c; 7 | extern const struct SomeStruct ss0; 8 | extern const struct StructWithPointers swp0, swp1; 9 | extern const struct StructWithPointers crossMod0; 10 | 11 | // integer constants used by globals_initialization_1.c 12 | const int x = 511; 13 | 14 | // struct constants used by globals_initialization_2.c 15 | const struct SomeStruct ss2 = { x * 3, x + 4, 1 }; 16 | 17 | // referring to constants in another module 18 | const struct StructWithPointers swp2 = { x, &c, &ss2, &swp0 }; 19 | const struct StructWithPointers swp3 = { x - 2, &swp0.field1, &ss2, &swp1 }; 20 | 21 | // a circular data structure, with links across modules 22 | const struct StructWithPointers crossMod1 = { 2, &crossMod0.field1, &ss0, &crossMod0}; 23 | 24 | // a struct with pointers to functions in another module 25 | const struct StructWithFunctionPointer swfp2 = { 21, &bar, (void*) &foo }; 26 | -------------------------------------------------------------------------------- /tests/functionptr_tests.rs: -------------------------------------------------------------------------------- 1 | use haybale::solver_utils::PossibleSolutions; 2 | use haybale::*; 3 | 4 | fn init_logging() { 5 | // capture log messages with test harness 6 | let _ = env_logger::builder().is_test(true).try_init(); 7 | } 8 | 9 | fn get_project() -> Project { 10 | let modname = "tests/bcfiles/functionptr.bc"; 11 | Project::from_bc_path(modname) 12 | .unwrap_or_else(|e| panic!("Failed to parse module {:?}: {}", modname, e)) 13 | } 14 | 15 | #[test] 16 | fn call_through_function_ptr() { 17 | let funcname = "fptr_driver"; 18 | init_logging(); 19 | let proj = get_project(); 20 | assert_eq!( 21 | get_possible_return_values_of_func(funcname, &proj, Config::default(), None, None, 5), 22 | PossibleSolutions::exactly_one(ReturnValue::Return(22)), 23 | ); 24 | } 25 | 26 | #[test] 27 | fn call_through_function_ptr_struct() { 28 | let funcname = "struct_driver"; 29 | let proj = get_project(); 30 | assert_eq!( 31 | get_possible_return_values_of_func(funcname, &proj, Config::default(), None, None, 5), 32 | PossibleSolutions::exactly_one(ReturnValue::Return(15)), 33 | ); 34 | } 35 | -------------------------------------------------------------------------------- /tests/linkedlist_tests.rs: -------------------------------------------------------------------------------- 1 | use haybale::*; 2 | 3 | fn init_logging() { 4 | // capture log messages with test harness 5 | let _ = env_logger::builder().is_test(true).try_init(); 6 | } 7 | 8 | fn get_project() -> Project { 9 | let modname = "tests/bcfiles/linkedlist.bc"; 10 | Project::from_bc_path(modname) 11 | .unwrap_or_else(|e| panic!("Failed to parse module {:?}: {}", modname, e)) 12 | } 13 | 14 | #[test] 15 | fn simple_linked_list() { 16 | let funcname = "simple_linked_list"; 17 | init_logging(); 18 | let proj = get_project(); 19 | let args = find_zero_of_func(funcname, &proj, Config::default(), None) 20 | .unwrap_or_else(|r| panic!("{}", r)) 21 | .expect("Failed to find zero of the function"); 22 | assert_eq!(args.len(), 1); 23 | assert_eq!(args[0], SolutionValue::I32(3)); 24 | } 25 | 26 | #[test] 27 | fn indirectly_recursive_type() { 28 | let funcname = "indirectly_recursive_type"; 29 | init_logging(); 30 | let proj = get_project(); 31 | let args = find_zero_of_func(funcname, &proj, Config::default(), None) 32 | .unwrap_or_else(|r| panic!("{}", r)) 33 | .expect("Failed to find zero of the function"); 34 | assert_eq!(args.len(), 1); 35 | assert_eq!(args[0], SolutionValue::I32(3)); 36 | } 37 | -------------------------------------------------------------------------------- /tests/bcfiles/linkedlist.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | // This test adapted from the linkedlist.c file in `llvm-ir`'s tests 4 | 5 | struct SimpleLinkedList { 6 | int val; 7 | struct SimpleLinkedList* next; 8 | }; 9 | 10 | int simple_linked_list(int x) { 11 | struct SimpleLinkedList list = { x, NULL }; 12 | list.val += 2; 13 | struct SimpleLinkedList list_1 = { x - 3, NULL }; 14 | struct SimpleLinkedList list_2 = { x * 5, NULL }; 15 | struct SimpleLinkedList list_3 = { x / 2, NULL }; 16 | struct SimpleLinkedList list_4 = { x / 100, NULL }; 17 | list.next = &list_1; 18 | list_1.next = &list_2; 19 | list_2.next = &list_3; 20 | list_3.next = &list_4; 21 | list_4.next = &list; 22 | return list.next->next->next->next->next->next->next->next->next->next->next->val; 23 | } 24 | 25 | // this type is indirectly recursive, unlike the directly recursive SimpleLinkedList type above 26 | struct NodeB; 27 | struct NodeA { 28 | int val; 29 | struct NodeB* b; 30 | }; 31 | 32 | struct NodeB { 33 | int val; 34 | struct NodeA* a; 35 | }; 36 | 37 | int indirectly_recursive_type(int x) { 38 | struct NodeA a = { x, NULL }; 39 | struct NodeB b = { x - 3, NULL }; 40 | struct NodeA a_1 = { x / 4, NULL }; 41 | a.b = &b; 42 | b.a = &a_1; 43 | a_1.b = &b; 44 | return a.b->a->b->a->b->a->b->val; 45 | } 46 | -------------------------------------------------------------------------------- /tests/bcfiles/memory.c: -------------------------------------------------------------------------------- 1 | int load_and_store(volatile int* ptr, int a) { 2 | *ptr = a - 3; 3 | return *ptr; 4 | } 5 | 6 | int local_ptr(int a) { 7 | volatile int stored; 8 | volatile int* ptr = &stored; 9 | *ptr = a - 3; 10 | return *ptr; 11 | } 12 | 13 | int overwrite(volatile int* ptr, int a) { 14 | *ptr = 0; 15 | *ptr = 2; 16 | *ptr = a - 3; 17 | return *ptr; 18 | } 19 | 20 | int load_and_store_mult(volatile int* ptr, int a) { 21 | *ptr = a; 22 | *ptr = *ptr + 10; 23 | *ptr = *ptr - 13; 24 | return *ptr; 25 | } 26 | 27 | int array(volatile int* ptr, int a) { 28 | ptr[10] = a - 3; 29 | ptr[0] = a + 3; 30 | return ptr[10]; 31 | } 32 | 33 | int pointer_arith(volatile int* ptr, int a) { 34 | volatile int* temp_ptr = ptr; 35 | *temp_ptr = a - 0; 36 | temp_ptr++; 37 | *temp_ptr = a - 1; 38 | temp_ptr++; 39 | *temp_ptr = a - 2; 40 | temp_ptr++; 41 | *temp_ptr = a - 3; 42 | temp_ptr++; 43 | *temp_ptr = a - 4; 44 | temp_ptr++; 45 | *temp_ptr = a - 5; 46 | temp_ptr++; 47 | *temp_ptr = a - 6; 48 | temp_ptr++; 49 | 50 | return ptr[3]; 51 | } 52 | 53 | int pointer_compare(int a) { 54 | volatile int x, y; 55 | volatile int* volatile ptr_x = &x; 56 | volatile int* volatile ptr_y = &y; 57 | 58 | *ptr_x = a - 3; 59 | 60 | if (ptr_x == ptr_y) { 61 | return *ptr_x - 100; 62 | } else { 63 | return *ptr_x; 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /tests/bcfiles/crossmod.c: -------------------------------------------------------------------------------- 1 | // Declarations of functions in call.c 2 | __attribute__((noinline)) int simple_callee(int x, int y); 3 | __attribute__((noinline)) int simple_caller(int x); 4 | 5 | // Declarations of functions and globals in globals.c 6 | extern volatile int global1, global2, global3; 7 | __attribute__((noinline)) int read_global(); 8 | __attribute__((noinline)) int modify_global(int x); 9 | __attribute__((noinline)) int modify_global_with_call(int x); 10 | 11 | // Identical to simple_caller in call.c. 12 | // The only difference is that this call crosses modules. 13 | __attribute__((noinline)) int cross_module_simple_caller(int x) { 14 | return simple_callee(x, 3); 15 | } 16 | 17 | // Identical to twice_caller in call.c. 18 | // The only difference is that these calls cross modules. 19 | int cross_module_twice_caller(int x) { 20 | return simple_callee(x, 5) + simple_callee(x, 1); 21 | } 22 | 23 | // Nested call on this side 24 | int cross_module_nested_near_caller(int x, int y) { 25 | return cross_module_simple_caller(x + y); 26 | } 27 | 28 | // Nested call on the far side 29 | int cross_module_nested_far_caller(int x, int y) { 30 | return simple_caller(x + y); 31 | } 32 | 33 | int cross_module_read_global() { 34 | return global1; 35 | } 36 | 37 | int cross_module_read_global_via_call() { 38 | return read_global(); 39 | } 40 | 41 | int cross_module_modify_global(int x) { 42 | global3 = x; 43 | return global3; 44 | } 45 | 46 | int cross_module_modify_global_via_call(int x) { 47 | modify_global(x); 48 | return global3; 49 | } 50 | -------------------------------------------------------------------------------- /tests/may_abort_tests.rs: -------------------------------------------------------------------------------- 1 | use haybale::solver_utils::PossibleSolutions; 2 | use haybale::*; 3 | 4 | fn init_logging() { 5 | // capture log messages with test harness 6 | let _ = env_logger::builder().is_test(true).try_init(); 7 | } 8 | 9 | fn get_abort_project() -> Project { 10 | let modname = "tests/bcfiles/abort.bc"; 11 | Project::from_bc_path(modname) 12 | .unwrap_or_else(|e| panic!("Failed to parse module {:?}: {}", modname, e)) 13 | } 14 | 15 | fn get_panic_project() -> Project { 16 | let modname = "tests/bcfiles/panic.bc"; 17 | Project::from_bc_path(modname) 18 | .unwrap_or_else(|e| panic!("Failed to parse module {:?}: {}", modname, e)) 19 | } 20 | 21 | #[test] 22 | fn may_exit() { 23 | let funcname = "may_exit"; 24 | init_logging(); 25 | let rvals = get_possible_return_values_of_func( 26 | funcname, 27 | &get_abort_project(), 28 | Config::default(), 29 | Some(vec![ParameterVal::Unconstrained]), 30 | None, 31 | 3, 32 | ); 33 | assert_eq!( 34 | rvals, 35 | PossibleSolutions::exactly_two(ReturnValue::Return(1), ReturnValue::Abort), 36 | ); 37 | } 38 | 39 | #[test] 40 | fn may_panic() { 41 | let funcname = "panic::may_panic"; 42 | init_logging(); 43 | let rvals = get_possible_return_values_of_func( 44 | funcname, 45 | &get_panic_project(), 46 | Config::default(), 47 | Some(vec![ParameterVal::Unconstrained]), 48 | None, 49 | 3, 50 | ); 51 | assert_eq!( 52 | rvals, 53 | PossibleSolutions::exactly_two(ReturnValue::Return(1), ReturnValue::Abort), 54 | ); 55 | } 56 | -------------------------------------------------------------------------------- /tests/bcfiles/globals_initialization_1.c: -------------------------------------------------------------------------------- 1 | // More complicated global-variable initialization, including referring to global variables in other modules 2 | 3 | #include "globals_initialization.h" 4 | 5 | // forward declarations (these are defined in globals_initialization_2.c) 6 | extern const int x; 7 | extern const struct SomeStruct ss2; 8 | extern const struct StructWithPointers crossMod1; 9 | 10 | // integer constants 11 | const int a = 2; // literal constant 12 | const int b = a; // refers to constant in same file 13 | const int c = b * 3; // function of constant in same file 14 | 15 | // struct constants 16 | const struct SomeStruct ss0 = { 0 }; 17 | const struct SomeStruct ss1 = { a, c, b }; 18 | 19 | // a circular data structure 20 | const struct StructWithPointers swp1; 21 | const struct StructWithPointers swp0 = { b, &x, &ss1, &swp1 }; 22 | const struct StructWithPointers swp1 = { c, &swp0.field1, &ss2, &swp0 }; 23 | 24 | // a circular data structure, with links across modules 25 | const struct StructWithPointers crossMod0 = { 2, &crossMod1.field1, &ss1, &crossMod1}; 26 | 27 | // a struct with pointers to functions in the same module 28 | const struct StructWithFunctionPointer swfp1 = { 21, &bar, (void*) &foo }; 29 | 30 | int foo() { 31 | return a // 2 32 | + b // 2 33 | + c // 6 34 | + ss0.field1 // 0 35 | + ss1.field2 // 6 36 | + ss2.field3 // 1 37 | + *(swp0.intptr) // 511 38 | + swp1.ssptr->field2 // 515 39 | + swp0.swpptr->swpptr->field1 // 2 40 | + *(crossMod0.swpptr->swpptr->intptr) // 2 41 | + swfp1.funcptr(2, 3); // 5 42 | } 43 | 44 | int bar(int x, int y) { 45 | return x + y; 46 | } 47 | -------------------------------------------------------------------------------- /tests/bcfiles/loop.c: -------------------------------------------------------------------------------- 1 | int while_loop(int end) { 2 | volatile int a = 0, i = 0; 3 | do { 4 | a++; 5 | } while (++i < end); 6 | return a - 3; 7 | } 8 | 9 | int for_loop(int end) { 10 | volatile int a = 0; 11 | for(int i = 0; i < end; i++) { 12 | a++; 13 | } 14 | return a - 3; 15 | } 16 | 17 | int loop_zero_iterations(int end) { 18 | volatile int a = 3; 19 | if(end < 0) return 1; // this way the function cannot return 0 when end < 0 20 | for(int i = 0; i < end; i++) { 21 | a++; 22 | } 23 | return a - 3; 24 | } 25 | 26 | int loop_with_cond(int end) { 27 | volatile int a = 0, i = 0; 28 | do { 29 | if(i % 3 == 0 || i > 6) a++; 30 | } while (++i < end); 31 | return a - 3; 32 | } 33 | 34 | int loop_inside_cond(int b) { 35 | volatile int a = 0; 36 | if (b > 7) { 37 | for (int i = 0; i < 3; i++) { 38 | a++; 39 | } 40 | } else { 41 | a = 2; 42 | } 43 | return a - 3; 44 | } 45 | 46 | int loop_over_array(int a) { 47 | volatile int arr[10]; 48 | for(int i = 0; i < 10; i++) { 49 | arr[i] = a - i; 50 | } 51 | return arr[3]; 52 | } 53 | 54 | int sum_of_array(int a) { 55 | volatile int arr[10]; 56 | for(int i = 0; i < 10; i++) { 57 | arr[i] = a; 58 | } 59 | int sum = 0; 60 | for(int i = 0; i < 10; i++) { 61 | sum += arr[i]; 62 | } 63 | return sum - 30; 64 | } 65 | 66 | int search_array(int a) { 67 | volatile int arr[10]; 68 | for(int i = 0; i < 10; i++) { 69 | arr[i] = i * 3; 70 | } 71 | int found = 0; 72 | for(int i = 0; i < 10; i++) { 73 | if (arr[i] > 9) { 74 | found = i; 75 | break; 76 | } 77 | } 78 | return a - found; 79 | } 80 | 81 | int nested_loop(int end) { 82 | volatile int a = 0; 83 | for(int i = 0; i < end; i++) { 84 | for (int j = 0; j < 10; j++) { 85 | a++; 86 | } 87 | } 88 | return a - 30; 89 | } 90 | -------------------------------------------------------------------------------- /tests/bcfiles/abort.ll: -------------------------------------------------------------------------------- 1 | ; ModuleID = 'abort.c' 2 | source_filename = "abort.c" 3 | target datalayout = "e-m:o-i64:64-f80:128-n8:16:32:64-S128" 4 | target triple = "x86_64-apple-macosx10.15.0" 5 | 6 | ; Function Attrs: nounwind ssp uwtable 7 | define i32 @may_exit(i32) local_unnamed_addr #0 { 8 | %2 = icmp sgt i32 %0, 2 9 | br i1 %2, label %3, label %4 10 | 11 | 3: ; preds = %1 12 | tail call void @exit(i32 1) #2 13 | unreachable 14 | 15 | 4: ; preds = %1 16 | ret i32 1 17 | } 18 | 19 | ; Function Attrs: noreturn 20 | declare void @exit(i32) local_unnamed_addr #1 21 | 22 | attributes #0 = { nounwind ssp uwtable "correctly-rounded-divide-sqrt-fp-math"="false" "disable-tail-calls"="false" "less-precise-fpmad"="false" "min-legal-vector-width"="0" "no-frame-pointer-elim"="true" "no-frame-pointer-elim-non-leaf" "no-infs-fp-math"="false" "no-jump-tables"="false" "no-nans-fp-math"="false" "no-signed-zeros-fp-math"="false" "no-trapping-math"="false" "stack-protector-buffer-size"="8" "target-cpu"="penryn" "target-features"="+cx16,+cx8,+fxsr,+mmx,+sahf,+sse,+sse2,+sse3,+sse4.1,+ssse3,+x87" "unsafe-fp-math"="false" "use-soft-float"="false" } 23 | attributes #1 = { noreturn "correctly-rounded-divide-sqrt-fp-math"="false" "disable-tail-calls"="false" "less-precise-fpmad"="false" "no-frame-pointer-elim"="true" "no-frame-pointer-elim-non-leaf" "no-infs-fp-math"="false" "no-nans-fp-math"="false" "no-signed-zeros-fp-math"="false" "no-trapping-math"="false" "stack-protector-buffer-size"="8" "target-cpu"="penryn" "target-features"="+cx16,+cx8,+fxsr,+mmx,+sahf,+sse,+sse2,+sse3,+sse4.1,+ssse3,+x87" "unsafe-fp-math"="false" "use-soft-float"="false" } 24 | attributes #2 = { noreturn nounwind } 25 | 26 | !llvm.module.flags = !{!0, !1} 27 | !llvm.ident = !{!2} 28 | 29 | !0 = !{i32 1, !"wchar_size", i32 4} 30 | !1 = !{i32 7, !"PIC Level", i32 2} 31 | !2 = !{!"clang version 9.0.1 "} 32 | -------------------------------------------------------------------------------- /src/test_utils.rs: -------------------------------------------------------------------------------- 1 | use crate::backend::DefaultBackend; 2 | use crate::{BBInstrIndex, Config, Location, Project, State}; 3 | use llvm_ir::module::DataLayout; 4 | use llvm_ir::types::Types; 5 | use llvm_ir::*; 6 | 7 | /// utility to initialize a `State` out of a `Project` and a function name 8 | pub fn blank_state<'p>(project: &'p Project, funcname: &str) -> State<'p, DefaultBackend> { 9 | let (func, module) = project 10 | .get_func_by_name(funcname) 11 | .expect("Failed to find function"); 12 | let start_loc = Location { 13 | module, 14 | func, 15 | bb: func 16 | .basic_blocks 17 | .get(0) 18 | .expect("Function must contain at least one basic block"), 19 | instr: BBInstrIndex::Instr(0), 20 | source_loc: None, 21 | }; 22 | State::new(project, start_loc, Config::default()) 23 | } 24 | 25 | /// Utility that creates a simple `Project` for testing. 26 | /// The `Project` will contain a single `Module` (with the given name) which contains 27 | /// a single function (given). 28 | pub fn blank_project(modname: impl Into, func: Function) -> Project { 29 | Project::from_module(Module { 30 | name: modname.into(), 31 | source_file_name: String::new(), 32 | data_layout: DataLayout::default(), 33 | target_triple: None, 34 | functions: vec![func], 35 | global_vars: vec![], 36 | global_aliases: vec![], 37 | inline_assembly: String::new(), 38 | types: Types::blank_for_testing(), 39 | }) 40 | } 41 | 42 | /// utility that creates a technically valid (but functionally useless) 43 | /// `Function` for testing 44 | /// 45 | /// the `Function` will contain technically valid (but functionally useless) 46 | /// `BasicBlock`s, one per name provided in `bbnames` 47 | pub fn blank_function(name: impl Into, bbnames: Vec) -> Function { 48 | let mut func = Function::new(name); 49 | for bbname in bbnames { 50 | func.basic_blocks.push(BasicBlock::new(bbname)); 51 | } 52 | func 53 | } 54 | -------------------------------------------------------------------------------- /src/alloc.rs: -------------------------------------------------------------------------------- 1 | use crate::cell_memory::Memory; 2 | use log::{debug, warn}; 3 | use std::collections::HashMap; 4 | 5 | /// An extremely simple bump-allocator which never frees 6 | #[derive(Clone)] 7 | pub struct Alloc { 8 | /// Pointer to available, unallocated memory 9 | cursor: u64, 10 | 11 | /// Map from allocation address to its size in bits 12 | sizes: HashMap, 13 | } 14 | 15 | impl Alloc { 16 | pub const ALLOC_START: u64 = 0x1000_0000; // we allocate from this address upwards 17 | 18 | pub fn new() -> Self { 19 | Self { 20 | cursor: Self::ALLOC_START, 21 | sizes: HashMap::new(), 22 | } 23 | } 24 | 25 | /// Allocate the specified number of bits, returning a pointer to the allocated object. 26 | // Internal invariants: 27 | // - for sizes <= cell size, allocation never crosses a cell boundary 28 | // - for sizes > cell size, allocation always starts at a cell boundary 29 | pub fn alloc(&mut self, bits: impl Into) -> u64 { 30 | let bits: u64 = bits.into(); 31 | if bits == 0 { 32 | warn!("An allocation of 0 bits was requested"); 33 | } 34 | let bits_in_byte: u64 = Memory::BITS_IN_BYTE.into(); 35 | let cell_bytes: u64 = Memory::CELL_BYTES.into(); 36 | let bytes = { 37 | let mut bytes = bits / bits_in_byte; 38 | if bits % bits_in_byte != 0 { 39 | bytes += 1; // round up to nearest byte 40 | } 41 | bytes 42 | }; 43 | let current_offset_bytes = self.cursor % cell_bytes; 44 | let bytes_remaining_in_cell = cell_bytes - current_offset_bytes; 45 | if bytes > bytes_remaining_in_cell { 46 | self.cursor += bytes_remaining_in_cell; 47 | assert_eq!(self.cursor % cell_bytes, 0); 48 | } 49 | let rval = self.cursor; 50 | self.cursor += bytes; 51 | self.sizes.insert(rval, bits); 52 | debug!("Allocated {} bits at 0x{:x}", bits, rval); 53 | rval 54 | } 55 | 56 | /// Get the size, in bits, of the allocation at the given address, or `None` 57 | /// if that address is not the result of an `alloc()`. 58 | pub fn get_allocation_size(&self, addr: impl Into) -> Option { 59 | self.sizes.get(&addr.into()).copied() 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /tests/bcfiles/basic.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | int no_args_zero() { 4 | return 0; 5 | } 6 | 7 | int no_args_nozero() { 8 | return 1; 9 | } 10 | 11 | int one_arg(int a) { 12 | return a - 3; 13 | } 14 | 15 | int two_args(int a, int b) { 16 | return a + b - 3; 17 | } 18 | 19 | int three_args(int a, int b, int c) { 20 | return a + b + c - 3; 21 | } 22 | 23 | int four_args(int a, int b, int c, int d) { 24 | return a + b + c + d - 3; 25 | } 26 | 27 | int five_args(int a, int b, int c, int d, int e) { 28 | return a + b + c + d + e - 3; 29 | } 30 | 31 | int binops(int a, int b) { 32 | int c = a + b - (77 * a) + 1; 33 | int d = (c & 23) / (a | 99); 34 | int e = (d ^ a) % (c << 3); 35 | return e >> d; 36 | } 37 | 38 | // this function can only return zero in its true branch 39 | int conditional_true(int a, int b) { 40 | if (a > b) { 41 | return (a-1) * (b-1); 42 | } else { 43 | return (a + b) % 3 + 10; 44 | } 45 | } 46 | 47 | // this function can only return zero in its false branch 48 | int conditional_false(int a, int b) { 49 | if (a > b) { 50 | return (a + b) % 3 + 10; 51 | } else { 52 | return (a-1) * (b-1); 53 | } 54 | } 55 | 56 | int conditional_nozero(int a, int b) { 57 | if (a > 2) { 58 | return a; 59 | } else if (b <= 0) { 60 | return b - 3; 61 | } else if (a <= 0) { 62 | return a - 7; 63 | } else { 64 | return a * b; 65 | } 66 | } 67 | 68 | int conditional_with_and(int a, int b) { 69 | if (a > 3 && b > 4) { 70 | return 0; 71 | } else { 72 | return 1; 73 | } 74 | } 75 | 76 | int has_switch(int a, int b) { 77 | switch (a - b) { 78 | case 0: return -1; 79 | case 1: return 3; 80 | case 2: return a - 3; 81 | case 3: return a * b + 1; 82 | case 33: return -300; 83 | case 451: return -5; 84 | default: return a - b - 1; 85 | } 86 | } 87 | 88 | int8_t int8t(int8_t a, int8_t b) { 89 | return a + b - 3; 90 | } 91 | 92 | int16_t int16t(int16_t a, int16_t b) { 93 | return a + b - 3; 94 | } 95 | 96 | int32_t int32t(int32_t a, int32_t b) { 97 | return a + b - 3; 98 | } 99 | 100 | int64_t int64t(int64_t a, int64_t b) { 101 | return a + b - 3; 102 | } 103 | 104 | int64_t mixed_bitwidths(int8_t i8, int16_t i16, int32_t i32, int64_t i64) { 105 | return i8 + i16 + i32 + i64 - 3; 106 | } 107 | -------------------------------------------------------------------------------- /tests/bcfiles/globals_initialization_2.ll: -------------------------------------------------------------------------------- 1 | ; ModuleID = 'globals_initialization_2.c' 2 | source_filename = "globals_initialization_2.c" 3 | target datalayout = "e-m:o-i64:64-f80:128-n8:16:32:64-S128" 4 | target triple = "x86_64-apple-macosx10.15.0" 5 | 6 | %struct.SomeStruct = type { i32, i32, i32 } 7 | %struct.StructWithPointers = type { i32, i32*, %struct.SomeStruct*, %struct.StructWithPointers* } 8 | %struct.StructWithFunctionPointer = type { i32, i32 (i32, i32)*, i8* } 9 | 10 | @x = local_unnamed_addr constant i32 511, align 4 11 | @ss2 = constant %struct.SomeStruct { i32 1533, i32 515, i32 1 }, align 4 12 | @c = external constant i32, align 4 13 | @swp0 = external constant %struct.StructWithPointers, align 8 14 | @swp2 = local_unnamed_addr constant %struct.StructWithPointers { i32 511, i32* @c, %struct.SomeStruct* @ss2, %struct.StructWithPointers* @swp0 }, align 8 15 | @swp1 = external constant %struct.StructWithPointers, align 8 16 | @swp3 = local_unnamed_addr constant %struct.StructWithPointers { i32 509, i32* getelementptr inbounds (%struct.StructWithPointers, %struct.StructWithPointers* @swp0, i32 0, i32 0), %struct.SomeStruct* @ss2, %struct.StructWithPointers* @swp1 }, align 8 17 | @crossMod0 = external constant %struct.StructWithPointers, align 8 18 | @ss0 = external constant %struct.SomeStruct, align 4 19 | @crossMod1 = local_unnamed_addr constant %struct.StructWithPointers { i32 2, i32* getelementptr inbounds (%struct.StructWithPointers, %struct.StructWithPointers* @crossMod0, i32 0, i32 0), %struct.SomeStruct* @ss0, %struct.StructWithPointers* @crossMod0 }, align 8 20 | @swfp2 = local_unnamed_addr constant %struct.StructWithFunctionPointer { i32 21, i32 (i32, i32)* @bar, i8* bitcast (i32 (...)* @foo to i8*) }, align 8 21 | 22 | declare i32 @bar(i32, i32) #0 23 | 24 | declare i32 @foo(...) #0 25 | 26 | attributes #0 = { "correctly-rounded-divide-sqrt-fp-math"="false" "disable-tail-calls"="false" "less-precise-fpmad"="false" "no-frame-pointer-elim"="true" "no-frame-pointer-elim-non-leaf" "no-infs-fp-math"="false" "no-nans-fp-math"="false" "no-signed-zeros-fp-math"="false" "no-trapping-math"="false" "stack-protector-buffer-size"="8" "target-cpu"="penryn" "target-features"="+cx16,+cx8,+fxsr,+mmx,+sahf,+sse,+sse2,+sse3,+sse4.1,+ssse3,+x87" "unsafe-fp-math"="false" "use-soft-float"="false" } 27 | 28 | !llvm.module.flags = !{!0, !1} 29 | !llvm.ident = !{!2} 30 | 31 | !0 = !{i32 1, !"wchar_size", i32 4} 32 | !1 = !{i32 7, !"PIC Level", i32 2} 33 | !2 = !{!"clang version 9.0.1 "} 34 | -------------------------------------------------------------------------------- /tests/bcfiles/Makefile: -------------------------------------------------------------------------------- 1 | CC=clang-9 2 | CXX=$$LLVM9PATH/bin/clang++ 3 | CFLAGS=-O3 4 | RUSTC=rustc 5 | RUSTFLAGS=--crate-type=lib 6 | RUST32BIT=--target i686-unknown-linux-gnu 7 | LLVMAS=$$LLVM9PATH/bin/llvm-as 8 | 9 | .PHONY: all 10 | all: basic.bc basic.ll \ 11 | issue_4.bc issue_4.ll \ 12 | issue_9.bc issue_9.ll \ 13 | issue_10.bc issue_10.ll \ 14 | memory.bc memory.ll \ 15 | loop.bc loop.ll \ 16 | struct.bc struct.ll \ 17 | struct-O3.bc struct-O3.ll \ 18 | linkedlist.bc linkedlist.ll \ 19 | call.bc call.ll \ 20 | crossmod.bc crossmod.ll \ 21 | globals.bc globals.ll \ 22 | globals_initialization_1.bc globals_initialization_1.ll \ 23 | globals_initialization_2.bc globals_initialization_2.ll \ 24 | functionptr.bc functionptr.ll \ 25 | simd.bc simd.ll \ 26 | simd_cl.bc simd_cl.ll \ 27 | throwcatch.bc throwcatch.ll \ 28 | abort.bc abort.ll \ 29 | panic.bc panic.ll \ 30 | atomicrmw.bc atomicrmw.ll \ 31 | 32bit/issue_4.bc 32bit/issue_4.ll \ 32 | 33 | %.ll : %.c 34 | $(CC) $(CFLAGS) -S -emit-llvm $^ -o $@ 35 | 36 | %.bc : %.c 37 | $(CC) $(CFLAGS) -c -emit-llvm $^ -o $@ 38 | 39 | %.ll : %.cpp 40 | $(CXX) $(CFLAGS) -S -emit-llvm $^ -o $@ 41 | 42 | %.bc : %.cpp 43 | $(CXX) $(CFLAGS) -c -emit-llvm $^ -o $@ 44 | 45 | %.ll : %.cl 46 | $(CC) $(CFLAGS) -Xclang -finclude-default-header -S -emit-llvm $^ -o $@ 47 | 48 | %.bc : %.cl 49 | $(CC) $(CFLAGS) -Xclang -finclude-default-header -c -emit-llvm $^ -o $@ 50 | 51 | %.ll : %.rs 52 | $(RUSTC) $(RUSTFLAGS) --emit=llvm-ir $^ -o $@ 53 | 54 | %.bc : %.rs 55 | $(RUSTC) $(RUSTFLAGS) --emit=llvm-bc $^ -o $@ 56 | 57 | 32bit/%.ll : %.rs 58 | mkdir -p 32bit 59 | $(RUSTC) $(RUSTFLAGS) $(RUST32BIT) --emit=llvm-ir $^ -o $@ 60 | 61 | 32bit/%.bc : %.rs 62 | mkdir -p 32bit 63 | $(RUSTC) $(RUSTFLAGS) $(RUST32BIT) --emit=llvm-bc $^ -o $@ 64 | 65 | # use -O1 on loop.c 66 | loop.ll : loop.c 67 | $(CC) -O1 -S -emit-llvm $^ -o $@ 68 | loop.bc : loop.c 69 | $(CC) -O1 -c -emit-llvm $^ -o $@ 70 | 71 | # use -O0 on struct.c and linkedlist.c 72 | struct.ll : struct.c 73 | $(CC) -O0 -S -emit-llvm $^ -o $@ 74 | struct.bc : struct.c 75 | $(CC) -O0 -c -emit-llvm $^ -o $@ 76 | linkedlist.ll : linkedlist.c 77 | $(CC) -O0 -S -emit-llvm $^ -o $@ 78 | linkedlist.bc : linkedlist.c 79 | $(CC) -O0 -c -emit-llvm $^ -o $@ 80 | 81 | # atomicrmw.ll is a .ll file written by hand, so we need to compile the .bc from the .ll 82 | atomicrmw.bc : atomicrmw.ll 83 | $(LLVMAS) $< -o $@ 84 | 85 | .PHONY: clean 86 | clean: 87 | find . -name "*.ll" | grep -v "atomicrmw.ll" | xargs rm 88 | find . -name "*.bc" | xargs rm 89 | find . -name "*~" | xargs rm 90 | -------------------------------------------------------------------------------- /src/callbacks.rs: -------------------------------------------------------------------------------- 1 | //! Functions and structures for defining and activating instruction callbacks 2 | 3 | use crate::backend::Backend; 4 | use crate::error::Result; 5 | use crate::state::State; 6 | use std::rc::Rc; 7 | 8 | #[derive(Clone)] 9 | pub struct Callbacks<'p, B: Backend> { 10 | /// `haybale` will call each of these functions before processing each 11 | /// LLVM non-terminator instruction. 12 | /// 13 | /// If the callback returns an `Err`, `haybale` will propagate it accordingly. 14 | #[allow(clippy::type_complexity)] 15 | pub(crate) instruction_callbacks: 16 | Vec) -> Result<()> + 'p>>, 17 | 18 | /// `haybale` will call each of these functions before processing each 19 | /// LLVM terminator instruction. 20 | /// 21 | /// If the callback returns an `Err`, `haybale` will propagate it accordingly. 22 | #[allow(clippy::type_complexity)] 23 | pub(crate) terminator_callbacks: 24 | Vec) -> Result<()> + 'p>>, 25 | } 26 | 27 | impl<'p, B: Backend> Callbacks<'p, B> { 28 | /// Add an instruction callback. `haybale` will call the provided function 29 | /// before processing each LLVM non-terminator instruction. 30 | /// 31 | /// If multiple instruction callbacks are added (by calling this function 32 | /// multiple times), `haybale` will call each of them before processing each 33 | /// instruction. 34 | /// 35 | /// If any callback returns an `Err`, `haybale` will propagate it accordingly. 36 | pub fn add_instruction_callback( 37 | &mut self, 38 | cb: impl Fn(&'p llvm_ir::Instruction, &State) -> Result<()> + 'p, 39 | ) { 40 | self.instruction_callbacks.push(Rc::new(cb)) 41 | } 42 | 43 | /// Add a terminator callback. `haybale` will call the provided function 44 | /// before processing each LLVM terminator instruction. 45 | /// 46 | /// If multiple terminator callbacks are added (by calling this function 47 | /// multiple times), `haybale` will call each of them before processing each 48 | /// terminator. 49 | /// 50 | /// If any callback returns an `Err`, `haybale` will propagate it accordingly. 51 | pub fn add_terminator_callback( 52 | &mut self, 53 | cb: impl Fn(&'p llvm_ir::Terminator, &State) -> Result<()> + 'p, 54 | ) { 55 | self.terminator_callbacks.push(Rc::new(cb)) 56 | } 57 | } 58 | 59 | impl<'p, B: Backend> Default for Callbacks<'p, B> { 60 | fn default() -> Self { 61 | Self { 62 | instruction_callbacks: Vec::new(), 63 | terminator_callbacks: Vec::new(), 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /tests/rmw_tests.rs: -------------------------------------------------------------------------------- 1 | #![cfg(not(feature = "llvm-9"))] // With LLVM 9 and earlier, Haybale doesn't support AtomicRMW 2 | 3 | use haybale::backend::DefaultBackend; 4 | use haybale::solver_utils::PossibleSolutions; 5 | use haybale::*; 6 | use llvm_ir::Name; 7 | 8 | fn init_logging() { 9 | // capture log messages with test harness 10 | let _ = env_logger::builder().is_test(true).try_init(); 11 | } 12 | 13 | fn get_project() -> Project { 14 | let modname = "tests/bcfiles/atomicrmw.bc"; 15 | Project::from_bc_path(modname) 16 | .unwrap_or_else(|e| panic!("Failed to parse module {:?}: {}", modname, e)) 17 | } 18 | 19 | #[test] 20 | fn atomicrmw() { 21 | init_logging(); 22 | let proj = get_project(); 23 | let funcname: String = "atomicrmwops".into(); 24 | let ret = get_possible_return_values_of_func( 25 | &funcname, 26 | &proj, 27 | Config::default(), 28 | Some(vec![ 29 | ParameterVal::ExactValue(0xFF00), 30 | ParameterVal::ExactValue(0x00FF), 31 | ]), 32 | None, 33 | 10, 34 | ); 35 | assert_eq!( 36 | ret, 37 | PossibleSolutions::exactly_one(ReturnValue::Return(0xFF00)) 38 | ); 39 | 40 | let mut em = symex_function( 41 | &funcname, 42 | &proj, 43 | Config::::default(), 44 | Some(vec![ 45 | ParameterVal::ExactValue(0xFF00), 46 | ParameterVal::Range(1, 3), 47 | ]), 48 | ).unwrap(); 49 | let _ = em 50 | .next() 51 | .unwrap() 52 | .map_err(|e| em.state().full_error_message_with_context(e)) 53 | .unwrap(); 54 | let var0 = em.state().get_bv_by_irname(&funcname, &Name::from(0)); 55 | let var1 = em.state().get_bv_by_irname(&funcname, &Name::from(1)); 56 | let var3 = em.state().get_bv_by_irname(&funcname, &Name::from(3)); 57 | let var4 = em.state().get_bv_by_irname(&funcname, &Name::from(4)); 58 | let var5 = em.state().get_bv_by_irname(&funcname, &Name::from(5)); 59 | let var6 = em.state().get_bv_by_irname(&funcname, &Name::from(6)); 60 | let var7 = em.state().get_bv_by_irname(&funcname, &Name::from(7)); 61 | let var8 = em.state().get_bv_by_irname(&funcname, &Name::from(8)); 62 | assert!(em.state().bvs_must_be_equal(var3, var0).unwrap()); 63 | assert!(em.state().bvs_must_be_equal(var4, var1).unwrap()); 64 | assert!(em.state().bvs_must_be_equal(var5, &var0.add(var1)).unwrap()); 65 | assert!(em.state().bvs_must_be_equal(var6, var0).unwrap()); 66 | assert!(em.state().bvs_must_be_equal(var7, &em.state().zero(var7.get_width())).unwrap()); // given the values we provided for %0 and %1, %7 must always be 0 67 | let sol8 = em.state().get_possible_solutions_for_bv(var8, 6).unwrap().as_u64_solutions().unwrap(); 68 | assert_eq!(sol8, PossibleSolutions::exactly_one(3)); 69 | } 70 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "haybale" 3 | version = "0.7.2" 4 | authors = ["Craig Disselkoen "] 5 | edition = "2018" 6 | description = "Symbolic execution of LLVM IR, written in Rust" 7 | documentation = "https://docs.rs/haybale" 8 | repository = "https://github.com/PLSysSec/haybale" 9 | readme = "README.md" 10 | keywords = ["symbolic", "llvm", "IR", "SMT"] 11 | license = "MIT" 12 | 13 | [dependencies] 14 | llvm-ir = "0.8.2" 15 | boolector = "0.4.3" 16 | either = "1.9" 17 | itertools = "0.11" 18 | reduce = "0.1" 19 | cpp_demangle = "0.2" 20 | rustc-demangle = "0.1" 21 | log = "0.4.20" 22 | rustversion = "1.0" 23 | 24 | [dev-dependencies] 25 | env_logger = "0.10" 26 | 27 | [features] 28 | # Select the LLVM version to be compatible with. 29 | # You _must_ enable exactly one of the following features. 30 | llvm-9 = ["llvm-ir/llvm-9", "llvm-9-or-lower", "llvm-9-or-greater"] 31 | llvm-10 = ["llvm-ir/llvm-10", "llvm-10-or-lower", "llvm-10-or-greater"] 32 | llvm-11 = ["llvm-ir/llvm-11", "llvm-11-or-lower", "llvm-11-or-greater"] 33 | llvm-12 = ["llvm-ir/llvm-12", "llvm-12-or-lower", "llvm-12-or-greater"] 34 | llvm-13 = ["llvm-ir/llvm-13", "llvm-13-or-lower", "llvm-13-or-greater"] 35 | llvm-14 = ["llvm-ir/llvm-14", "llvm-14-or-lower", "llvm-14-or-greater"] 36 | 37 | # If you enable this, Cargo will automatically download and build Boolector as 38 | # part of the build process. If you don't enable this, Cargo will look for a 39 | # system install of Boolector as a shared library. 40 | vendor-boolector = ["boolector/vendor-lgl"] 41 | 42 | ### 43 | # For convenience, these automatically-enabled features allow us to avoid 44 | # checking complex combinations of features all the time. They are not meant to 45 | # be manually enabled; use the above llvm-x features instead 46 | llvm-9-or-greater = [] 47 | llvm-10-or-greater = ["llvm-9-or-greater"] 48 | llvm-11-or-greater = ["llvm-10-or-greater"] 49 | llvm-12-or-greater = ["llvm-11-or-greater"] 50 | llvm-13-or-greater = ["llvm-12-or-greater"] 51 | llvm-14-or-greater = ["llvm-13-or-greater"] 52 | 53 | llvm-9-or-lower = ["llvm-10-or-lower"] 54 | llvm-10-or-lower = ["llvm-11-or-lower"] 55 | llvm-11-or-lower = ["llvm-12-or-lower"] 56 | llvm-12-or-lower = ["llvm-13-or-lower"] 57 | llvm-13-or-lower = ["llvm-14-or-lower"] 58 | llvm-14-or-lower = [] 59 | ### 60 | 61 | # These features select the corresponding LLVM version, and require an exact 62 | # match between the system LLVM version and the LLVM version chosen here. For 63 | # more information, see the "strict-versioning" feature on `llvm-sys`. 64 | llvm-9-strict = ["llvm-9", "llvm-ir/llvm-9-strict"] 65 | llvm-10-strict = ["llvm-10", "llvm-ir/llvm-10-strict"] 66 | llvm-11-strict = ["llvm-11", "llvm-ir/llvm-11-strict"] 67 | llvm-12-strict = ["llvm-12", "llvm-ir/llvm-12-strict"] 68 | llvm-13-strict = ["llvm-13", "llvm-ir/llvm-13-strict"] 69 | llvm-14-strict = ["llvm-14", "llvm-ir/llvm-14-strict"] 70 | 71 | [package.metadata.docs.rs] 72 | # Generate docs.rs documentation with the llvm-10 feature 73 | features = ["llvm-10"] 74 | -------------------------------------------------------------------------------- /tests/bcfiles/globals.ll: -------------------------------------------------------------------------------- 1 | ; ModuleID = 'globals.c' 2 | source_filename = "globals.c" 3 | target datalayout = "e-m:o-i64:64-f80:128-n8:16:32:64-S128" 4 | target triple = "x86_64-apple-macosx10.15.0" 5 | 6 | @global1 = global i32 3, align 4 7 | @global2 = global i32 5, align 4 8 | @global3 = common global i32 0, align 4 9 | 10 | ; Function Attrs: nofree noinline norecurse nounwind ssp uwtable 11 | define i32 @read_global() local_unnamed_addr #0 { 12 | %1 = load volatile i32, i32* @global1, align 4, !tbaa !3 13 | ret i32 %1 14 | } 15 | 16 | ; Function Attrs: nofree noinline norecurse nounwind ssp uwtable 17 | define i32 @modify_global(i32) local_unnamed_addr #0 { 18 | store volatile i32 %0, i32* @global3, align 4, !tbaa !3 19 | %2 = load volatile i32, i32* @global3, align 4, !tbaa !3 20 | ret i32 %2 21 | } 22 | 23 | ; Function Attrs: nofree noinline norecurse nounwind ssp uwtable 24 | define i32 @modify_global_with_call(i32) local_unnamed_addr #0 { 25 | %2 = tail call i32 @modify_global(i32 %0) 26 | %3 = load volatile i32, i32* @global3, align 4, !tbaa !3 27 | ret i32 %3 28 | } 29 | 30 | ; Function Attrs: nofree norecurse nounwind ssp uwtable 31 | define i32 @dont_confuse_globals(i32) local_unnamed_addr #1 { 32 | store volatile i32 100, i32* @global1, align 4, !tbaa !3 33 | store volatile i32 95, i32* @global2, align 4, !tbaa !3 34 | store volatile i32 %0, i32* @global3, align 4, !tbaa !3 35 | %2 = load volatile i32, i32* @global2, align 4, !tbaa !3 36 | %3 = add nsw i32 %2, -200 37 | store volatile i32 %3, i32* @global1, align 4, !tbaa !3 38 | %4 = load volatile i32, i32* @global3, align 4, !tbaa !3 39 | ret i32 %4 40 | } 41 | 42 | attributes #0 = { nofree noinline norecurse nounwind ssp uwtable "correctly-rounded-divide-sqrt-fp-math"="false" "disable-tail-calls"="false" "less-precise-fpmad"="false" "min-legal-vector-width"="0" "no-frame-pointer-elim"="true" "no-frame-pointer-elim-non-leaf" "no-infs-fp-math"="false" "no-jump-tables"="false" "no-nans-fp-math"="false" "no-signed-zeros-fp-math"="false" "no-trapping-math"="false" "stack-protector-buffer-size"="8" "target-cpu"="penryn" "target-features"="+cx16,+cx8,+fxsr,+mmx,+sahf,+sse,+sse2,+sse3,+sse4.1,+ssse3,+x87" "unsafe-fp-math"="false" "use-soft-float"="false" } 43 | attributes #1 = { nofree norecurse nounwind ssp uwtable "correctly-rounded-divide-sqrt-fp-math"="false" "disable-tail-calls"="false" "less-precise-fpmad"="false" "min-legal-vector-width"="0" "no-frame-pointer-elim"="true" "no-frame-pointer-elim-non-leaf" "no-infs-fp-math"="false" "no-jump-tables"="false" "no-nans-fp-math"="false" "no-signed-zeros-fp-math"="false" "no-trapping-math"="false" "stack-protector-buffer-size"="8" "target-cpu"="penryn" "target-features"="+cx16,+cx8,+fxsr,+mmx,+sahf,+sse,+sse2,+sse3,+sse4.1,+ssse3,+x87" "unsafe-fp-math"="false" "use-soft-float"="false" } 44 | 45 | !llvm.module.flags = !{!0, !1} 46 | !llvm.ident = !{!2} 47 | 48 | !0 = !{i32 1, !"wchar_size", i32 4} 49 | !1 = !{i32 7, !"PIC Level", i32 2} 50 | !2 = !{!"clang version 9.0.1 "} 51 | !3 = !{!4, !4, i64 0} 52 | !4 = !{!"int", !5, i64 0} 53 | !5 = !{!"omnipotent char", !6, i64 0} 54 | !6 = !{!"Simple C/C++ TBAA"} 55 | -------------------------------------------------------------------------------- /tests/bcfiles/throwcatch.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | // this function extern "C" so we don't have to worry about demangling 4 | extern "C" { 5 | 6 | // this function never throws, and should always return a positive number 7 | int doesnt_throw(int a) { 8 | volatile int x = 0; 9 | volatile bool b = false; 10 | try { 11 | x = 10; 12 | if(b) throw (int32_t)20; 13 | } catch (...) { 14 | return -1; 15 | } 16 | x++; 17 | try { 18 | if(b) throw (int32_t)20; 19 | if (x + a < 100) { 20 | return 1; 21 | } else { 22 | return 2; 23 | } 24 | } catch (...) { 25 | return -2; 26 | } 27 | } 28 | 29 | // remaining functions are not extern "C", to test demangling 30 | } 31 | 32 | // this function either returns 2 or throws 20 33 | int throw_uncaught(volatile int a) { 34 | if (a % 2) { 35 | return 2; 36 | } else { 37 | throw (int32_t)20; 38 | } 39 | } 40 | 41 | // this function may return 1 or 2, or throw 3 or 4 42 | int throw_multiple_values(volatile int a) { 43 | switch (a % 4) { 44 | case 1: return 1; 45 | case 2: return 2; 46 | case 3: throw (int32_t)3; 47 | default: throw (int32_t)4; 48 | } 49 | } 50 | 51 | // this function either returns 2 or throws 20 52 | int throw_uncaught_wrongtype(volatile int a) { 53 | try { 54 | if (a % 2) { 55 | return 2; 56 | } else { 57 | throw (int32_t)20; 58 | } 59 | } catch (unsigned char c) { 60 | return 10; 61 | } 62 | } 63 | 64 | // here's a void function that may throw a value 65 | __attribute__((noinline)) void throw_uncaught_void(volatile int* a) { 66 | if (*a == 0) { 67 | *a = 1; 68 | } else { 69 | throw (int32_t)20; 70 | } 71 | } 72 | 73 | // this function either returns 1 or throws 20 74 | int throw_uncaught_caller(int a) { 75 | volatile int x = a; 76 | throw_uncaught_void(&x); 77 | return 1; 78 | } 79 | 80 | // here we can either return 2 or 5 81 | int throw_and_catch_wildcard(bool shouldthrow) { 82 | try { 83 | if(shouldthrow) throw (int32_t)20; 84 | return 2; 85 | } catch (...) { 86 | return 5; 87 | } 88 | } 89 | 90 | // here we can either return 2 or 20 91 | int throw_and_catch_val(bool shouldthrow) { 92 | try { 93 | if(shouldthrow) throw (int32_t)20; 94 | return 2; 95 | } catch (int e) { 96 | return e; 97 | } 98 | } 99 | 100 | // here we should still return either 2 or 20 101 | int throw_and_catch_in_caller(bool shouldthrow) { 102 | volatile int x = 2; 103 | try { 104 | if(shouldthrow) throw_uncaught_void(&x); 105 | } catch (int e) { 106 | return e; 107 | } 108 | return 2; 109 | } 110 | 111 | // here we should return 2 or throw 20 112 | int throw_and_rethrow_in_caller(bool shouldthrow) { 113 | volatile int x = 2; 114 | try { 115 | if(shouldthrow) throw_uncaught_void(&x); 116 | } 117 | catch (int e) { 118 | throw; 119 | } 120 | return 2; 121 | } 122 | -------------------------------------------------------------------------------- /tests/hook_tests.rs: -------------------------------------------------------------------------------- 1 | use haybale::backend::Backend; 2 | use haybale::function_hooks::IsCall; 3 | use haybale::solver_utils::PossibleSolutions; 4 | use haybale::*; 5 | 6 | fn init_logging() { 7 | // capture log messages with test harness 8 | let _ = env_logger::builder().is_test(true).try_init(); 9 | } 10 | 11 | // Hook call.c's "simple_callee" to just return 5 instead of executing its actual body 12 | fn hook_for_simple_callee<'p, B: Backend>( 13 | state: &mut State<'p, B>, 14 | call: &'p dyn IsCall, 15 | ) -> Result> { 16 | assert_eq!(call.get_arguments().len(), 2); 17 | let ret_size = state.size_in_bits(&state.type_of(call)).ok_or_else(|| { 18 | Error::OtherError("simple_callee shouldn't return opaque struct type".into()) 19 | })?; 20 | assert_ne!(ret_size, 0); 21 | Ok(ReturnValue::Return(state.bv_from_u32(5, ret_size))) 22 | } 23 | 24 | #[test] 25 | fn hook_a_function() { 26 | init_logging(); 27 | let proj = Project::from_bc_path("tests/bcfiles/call.bc") 28 | .unwrap_or_else(|e| panic!("Failed to parse module call.bc: {}", e)); 29 | let mut config = Config::default(); 30 | config 31 | .function_hooks 32 | .add("simple_callee", &hook_for_simple_callee); 33 | // with that hook, simple_caller should always return 5 regardless of the value of its argument 34 | assert_eq!( 35 | get_possible_return_values_of_func("simple_caller", &proj, config, None, None, 3), 36 | PossibleSolutions::exactly_one(ReturnValue::Return(5)), 37 | ); 38 | } 39 | 40 | // Hook functionptr.c's "get_function_ptr" to return a pointer to our hook "target_hook" instead of "foo" or "bar" like it normally does 41 | fn hook_for_get_function_ptr<'p, B: Backend>( 42 | state: &mut State<'p, B>, 43 | call: &'p dyn IsCall, 44 | ) -> Result> { 45 | assert_eq!(call.get_arguments().len(), 1); 46 | state 47 | .get_pointer_to_function_hook("asdfjkl") 48 | .cloned() 49 | .ok_or_else(|| Error::OtherError("Failed to get a pointer to function hook".to_owned())) 50 | .map(ReturnValue::Return) 51 | } 52 | 53 | fn target_hook<'p, B: Backend>( 54 | state: &mut State<'p, B>, 55 | call: &'p dyn IsCall, 56 | ) -> Result> { 57 | assert_eq!(call.get_arguments().len(), 2); 58 | let ret_size = state.size_in_bits(&state.type_of(call)).ok_or_else(|| { 59 | Error::OtherError("target_hook: call return type shouldn't be opaque struct type".into()) 60 | })?; 61 | assert_ne!(ret_size, 0); 62 | Ok(ReturnValue::Return(state.bv_from_u32(5, ret_size))) 63 | } 64 | 65 | #[test] 66 | fn hook_a_function_ptr() { 67 | init_logging(); 68 | let proj = Project::from_bc_path("tests/bcfiles/functionptr.bc") 69 | .unwrap_or_else(|e| panic!("Failed to parse module functionptr.bc: {}", e)); 70 | let mut config = Config::default(); 71 | config 72 | .function_hooks 73 | .add("get_function_ptr", &hook_for_get_function_ptr); 74 | // With the current API, in order for `state.get_pointer_to_function_hook()` 75 | // to work inside `hook_for_get_function_ptr`, we need to register 76 | // `target_hook` as the hook for some function name, doesn't matter what 77 | config.function_hooks.add("asdfjkl", &target_hook); 78 | // with these hooks, now `get_function_ptr` should return a pointer to `target_hook` instead of `foo` like it normally does, 79 | // and therefore fptr_driver() should return 15 instead of 22 80 | assert_eq!( 81 | get_possible_return_values_of_func("fptr_driver", &proj, config, None, None, 3), 82 | PossibleSolutions::exactly_one(ReturnValue::Return(15)), 83 | ); 84 | } 85 | -------------------------------------------------------------------------------- /src/hooks/exceptions.rs: -------------------------------------------------------------------------------- 1 | //! Default hooks for C++ exception-related functions 2 | 3 | use crate::alloc_utils; 4 | use crate::backend::{Backend, BV}; 5 | use crate::error::*; 6 | use crate::function_hooks::IsCall; 7 | use crate::return_value::*; 8 | use crate::state::State; 9 | use llvm_ir::*; 10 | 11 | pub fn cxa_allocate_exception( 12 | state: &mut State, 13 | call: &dyn IsCall, 14 | ) -> Result> { 15 | assert_eq!(call.get_arguments().len(), 1); 16 | let bytes = &call.get_arguments()[0].0; 17 | 18 | // sanity-check argument types 19 | match state.type_of(bytes).as_ref() { 20 | Type::IntegerType { .. } => {}, 21 | ty => { 22 | return Err(Error::OtherError(format!( 23 | "__cxa_allocate_exception: expected argument to have integer type, but got {:?}", 24 | ty 25 | ))) 26 | }, 27 | }; 28 | match state.type_of(call).as_ref() { 29 | Type::PointerType { .. } => {}, 30 | ty => { 31 | return Err(Error::OtherError(format!( 32 | "__cxa_allocate_exception: expected return type to be a pointer type, but got {:?}", 33 | ty 34 | ))) 35 | }, 36 | }; 37 | 38 | let addr = alloc_utils::zalloc(state, bytes)?; 39 | Ok(ReturnValue::Return(addr)) 40 | } 41 | 42 | pub fn cxa_throw( 43 | state: &mut State, 44 | call: &dyn IsCall, 45 | ) -> Result> { 46 | assert_eq!(call.get_arguments().len(), 3); 47 | let thrown_ptr = &call.get_arguments()[0].0; 48 | let type_info = &call.get_arguments()[1].0; 49 | 50 | // sanity-check argument types 51 | match state.type_of(thrown_ptr).as_ref() { 52 | Type::PointerType { .. } => {}, 53 | ty => { 54 | return Err(Error::OtherError(format!( 55 | "__cxa_throw: expected first argument to be some pointer type, got {:?}", 56 | ty 57 | ))) 58 | }, 59 | } 60 | match state.type_of(type_info).as_ref() { 61 | Type::PointerType { .. } => {}, 62 | ty => { 63 | return Err(Error::OtherError(format!( 64 | "__cxa_throw: expected second argument to be some pointer type, got {:?}", 65 | ty 66 | ))) 67 | }, 68 | } 69 | 70 | let thrown_ptr = state.operand_to_bv(thrown_ptr)?; 71 | Ok(ReturnValue::Throw(thrown_ptr)) 72 | } 73 | 74 | pub fn cxa_begin_catch( 75 | state: &mut State, 76 | call: &dyn IsCall, 77 | ) -> Result> { 78 | assert_eq!(call.get_arguments().len(), 1); 79 | 80 | // Since we're not doing anything fancy with exception frames (our exceptions are just pointers to the thrown values), 81 | // our __cxa_begin_catch() just returns its argument directly. 82 | // See [this section of the LLVM docs on exception handling](https://releases.llvm.org/9.0.0/docs/ExceptionHandling.html#try-catch). 83 | let arg = &call.get_arguments()[0].0; 84 | let arg = state.operand_to_bv(arg)?; 85 | Ok(ReturnValue::Return(arg)) 86 | } 87 | 88 | pub fn cxa_end_catch( 89 | _state: &mut State, 90 | _call: &dyn IsCall, 91 | ) -> Result> { 92 | // Since we don't free things anyway, we don't need to worry about freeing the exception, and 93 | // __cxa_end_catch() can be a no-op 94 | Ok(ReturnValue::ReturnVoid) 95 | } 96 | 97 | pub fn llvm_eh_typeid_for( 98 | state: &mut State, 99 | call: &dyn IsCall, 100 | ) -> Result> { 101 | assert_eq!(call.get_arguments().len(), 1); 102 | 103 | // for now we ignore the argument and return an unconstrained value 104 | // (unconstrained except for the constraint that the value is positive, as specified in LLVM docs) 105 | let retval = state.new_bv_with_name(Name::from("llvm_eh_typeid_for_retval"), 32)?; 106 | retval.sgte(&state.zero(32)).assert()?; 107 | Ok(ReturnValue::Return(retval)) 108 | } 109 | -------------------------------------------------------------------------------- /tests/bcfiles/call.c: -------------------------------------------------------------------------------- 1 | __attribute__((noinline)) int simple_callee(int x, int y) { 2 | return x - y; 3 | } 4 | 5 | __attribute__((noinline)) int simple_caller(int x) { 6 | return simple_callee(x, 3); 7 | } 8 | 9 | int conditional_caller(int x, int y) { 10 | if (y > 5) { 11 | return simple_callee(x, 3); 12 | } else { 13 | return y + 10; 14 | } 15 | } 16 | 17 | int twice_caller(int x) { 18 | return simple_callee(x, 5) + simple_callee(x, 1); 19 | } 20 | 21 | int nested_caller(int x, int y) { 22 | return simple_caller(x + y); 23 | } 24 | 25 | __attribute__((noinline)) int callee_with_loop(int x, int y) { 26 | volatile int a = 0; 27 | for (volatile int i = 0; i < x; i++) { 28 | a += 10; 29 | } 30 | return a - (y + 27); 31 | } 32 | 33 | int caller_of_loop(int x) { 34 | return callee_with_loop(x, 3); 35 | } 36 | 37 | int caller_with_loop(int x) { 38 | int a = 0; 39 | for (volatile int i = 0; i < x; i++) { 40 | a += simple_callee(a + 3, 1); 41 | } 42 | return a - 14; 43 | } 44 | 45 | __attribute__((noinline)) int recursive_simple(int x) { 46 | int y = x * 2; 47 | if (x < -1000) { 48 | return -1; 49 | } else if (y > 25) { 50 | return y; 51 | } else { 52 | return recursive_simple(y) - 44; 53 | } 54 | } 55 | 56 | __attribute__((noinline)) int recursive_double(int x) { 57 | int y = x * 2; 58 | if (x < -1000) { 59 | return -1; 60 | } else if (y > 1000) { 61 | return y; 62 | } else if (y > 25) { 63 | return recursive_double(y + 7) + 1; 64 | } else if (y < -10) { 65 | return recursive_double(-y) - 1; 66 | } else { 67 | return y - 23; 68 | } 69 | } 70 | 71 | __attribute__((noinline)) int recursive_not_tail(int x) { 72 | if (x > 100) return x + 10; 73 | int a = recursive_not_tail(x + 20); 74 | if (a % 2 == 0) return a % 3; 75 | return (a % 5) - 8; 76 | } 77 | 78 | __attribute__((noinline)) int recursive_and_normal_caller(int x) { 79 | if (x < 0) return -1; 80 | int y = x * 2; 81 | if (simple_callee(y, 3) > 25) return y; 82 | return recursive_and_normal_caller(y) - 44; 83 | } 84 | 85 | __attribute__((noinline)) int mutually_recursive_b(int x); 86 | __attribute__((noinline)) int mutually_recursive_a(int x) { 87 | int u = 5; 88 | if (x > u) return x; 89 | return mutually_recursive_b(x * 2) - 1; 90 | } 91 | 92 | __attribute__((noinline)) int mutually_recursive_b(int x) { 93 | int j = 2; 94 | int k = 2; 95 | if (x < 0) return x; 96 | return mutually_recursive_a(x - k) - j; 97 | } 98 | 99 | // for mutually_recursive_a(x) to return 0, 100 | // x must be <= u and mutually_recursive_b(x*2) must be 1 101 | // x must be <= u and x*2 must be >= 0 and mutually_recursive_a(x*2 - k) must be j+1 102 | // 0 <= x <= u and mutually_recursive_a(2x - k) = j+1 103 | // 0 <= x <= u and (2x - k) <= u and mutually_recursive_b((2x - k)*2) = j+2 104 | // 0 <= x <= u and (2x - k) <= u and mutually_recursive_b(4x - 2k) = j+2 105 | // 0 <= x <= u and (2x - k) <= u and (4x - 2k) >= 0 and mutually_recursive_a(4x - 2k - k) = 2j+2 106 | // 0 <= x <= u and (2x - k) <= u and (2x - k) >= 0 and mutually_recursive_a(4x - 3k) = 2j+2 107 | // 0 <= x <= u and 0 <= (2x - k) <= u and mutually_recursive_a(4x - 3k) = 2j+2 108 | // if the recursion ends here then u < 2j+2 and 4x - 3k = 2j+2 109 | // 0 <= x <= u < 2j+2 = 4x - 3k and 0 <= 2x - k <= u 110 | // Any satisfying solution must have x < 4x - 3k 111 | // 0 < x - k 112 | // k < x 113 | // Try x = 3, u = 3, k = 0, 4x - 3k = 12, 2j+2 = 12 => j = 5, 2x - k = 6 which violates 2x - k <= u 114 | // Is there a solution when x = 3? Then we would need to have 115 | // 0 <= 3 <= u < 2j+2 = 12 - 3k and 0 <= 6 - k <= u 116 | // Try k = 3 then 3 <= u < 2j+2 = 3 no 117 | // Try k = 2 then 3 <= u < 2j+2 = 6 and 0 <= 4 <= u 118 | // implies j = 2, and u can be 5 119 | // CHECK: 120 | // mutually_recursive_a(3) 121 | // = mutually_recursive_b(6) - 1 122 | // = (mutually_recursive_a(4) - 2) - 1 123 | // = mutually_recursive_a(4) - 3 124 | // = (mutually_recursive_b(8) - 1) - 3 125 | // = mutually_recursive_b(8) - 4 126 | // = (mutually_recursive_a(6) - 2) - 4 127 | // = mutually_recursive_a(6) - 6 128 | // = 6 - 6 129 | // = 0 130 | -------------------------------------------------------------------------------- /tests/loop_tests.rs: -------------------------------------------------------------------------------- 1 | use haybale::*; 2 | 3 | fn init_logging() { 4 | // capture log messages with test harness 5 | let _ = env_logger::builder().is_test(true).try_init(); 6 | } 7 | 8 | fn get_project() -> Project { 9 | let modname = "tests/bcfiles/loop.bc"; 10 | Project::from_bc_path(modname) 11 | .unwrap_or_else(|e| panic!("Failed to parse module {:?}: {}", modname, e)) 12 | } 13 | 14 | #[test] 15 | fn while_loop() { 16 | let funcname = "while_loop"; 17 | init_logging(); 18 | let proj = get_project(); 19 | let args = find_zero_of_func(funcname, &proj, Config::default(), None) 20 | .unwrap_or_else(|r| panic!("{}", r)) 21 | .expect("Failed to find zero of the function"); 22 | assert_eq!(args.len(), 1); 23 | assert_eq!(args[0], SolutionValue::I32(3)); 24 | } 25 | 26 | #[test] 27 | fn for_loop() { 28 | let funcname = "for_loop"; 29 | init_logging(); 30 | let proj = get_project(); 31 | let args = find_zero_of_func(funcname, &proj, Config::default(), None) 32 | .unwrap_or_else(|r| panic!("{}", r)) 33 | .expect("Failed to find zero of the function"); 34 | assert_eq!(args.len(), 1); 35 | assert_eq!(args[0], SolutionValue::I32(3)); 36 | } 37 | 38 | #[test] 39 | fn loop_zero_iterations() { 40 | let funcname = "loop_zero_iterations"; 41 | init_logging(); 42 | let proj = get_project(); 43 | let args = find_zero_of_func(funcname, &proj, Config::default(), None) 44 | .unwrap_or_else(|r| panic!("{}", r)) 45 | .expect("Failed to find zero of the function"); 46 | assert_eq!(args.len(), 1); 47 | assert_eq!(args[0], SolutionValue::I32(0)); 48 | } 49 | 50 | #[test] 51 | fn loop_with_cond() { 52 | let funcname = "loop_with_cond"; 53 | init_logging(); 54 | let proj = get_project(); 55 | let args = find_zero_of_func(funcname, &proj, Config::default(), None) 56 | .unwrap_or_else(|r| panic!("{}", r)) 57 | .expect("Failed to find zero of the function"); 58 | assert_eq!(args.len(), 1); 59 | assert_eq!(args[0], SolutionValue::I32(7)); 60 | } 61 | 62 | #[test] 63 | fn loop_inside_cond() { 64 | let funcname = "loop_inside_cond"; 65 | init_logging(); 66 | let proj = get_project(); 67 | let args = find_zero_of_func(funcname, &proj, Config::default(), None) 68 | .unwrap_or_else(|r| panic!("{}", r)) 69 | .expect("Failed to find zero of the function"); 70 | assert_eq!(args.len(), 1); 71 | assert!(args[0].unwrap_to_i32() > 7); 72 | } 73 | 74 | #[test] 75 | fn loop_over_array() { 76 | let funcname = "loop_over_array"; 77 | init_logging(); 78 | let proj = get_project(); 79 | let args = find_zero_of_func(funcname, &proj, Config::default(), None) 80 | .unwrap_or_else(|r| panic!("{}", r)) 81 | .expect("Failed to find zero of the function"); 82 | assert_eq!(args.len(), 1); 83 | assert_eq!(args[0], SolutionValue::I32(3)); 84 | } 85 | 86 | #[test] 87 | fn sum_of_array() { 88 | let funcname = "sum_of_array"; 89 | init_logging(); 90 | let proj = get_project(); 91 | let args = find_zero_of_func(funcname, &proj, Config::default(), None) 92 | .unwrap_or_else(|r| panic!("{}", r)) 93 | .expect("Failed to find zero of the function"); 94 | assert_eq!(args.len(), 1); 95 | assert_eq!(args[0], SolutionValue::I32(3)); 96 | } 97 | 98 | #[test] 99 | fn search_array() { 100 | let funcname = "search_array"; 101 | init_logging(); 102 | let proj = get_project(); 103 | let args = find_zero_of_func(funcname, &proj, Config::default(), None) 104 | .unwrap_or_else(|r| panic!("{}", r)) 105 | .expect("Failed to find zero of the function"); 106 | assert_eq!(args.len(), 1); 107 | assert_eq!(args[0], SolutionValue::I32(4)); 108 | } 109 | 110 | #[test] 111 | fn nested_loop() { 112 | let funcname = "nested_loop"; 113 | init_logging(); 114 | let proj = get_project(); 115 | let mut config = Config::default(); 116 | config.loop_bound = 50; 117 | let args = find_zero_of_func(funcname, &proj, config, None) 118 | .unwrap_or_else(|r| panic!("{}", r)) 119 | .expect("Failed to find zero of the function"); 120 | assert_eq!(args.len(), 1); 121 | assert_eq!(args[0], SolutionValue::I32(3)); 122 | } 123 | -------------------------------------------------------------------------------- /src/double_keyed_map.rs: -------------------------------------------------------------------------------- 1 | //! This implementation is taken almost entirely from 2 | //! https://stackoverflow.com/questions/45786717/how-to-implement-hashmap-with-two-keys/45795699. 3 | 4 | // we have some methods on `DoubleKeyedMap` that may not currently be used by callers, but they still make sense to be part of `DoubleKeyedMap` 5 | #![allow(dead_code)] 6 | 7 | use std::borrow::Borrow; 8 | use std::collections::HashMap; 9 | use std::hash::{Hash, Hasher}; 10 | 11 | #[derive(PartialEq, Eq, Hash)] 12 | pub struct Pair(A, B); 13 | 14 | impl Clone for Pair { 15 | fn clone(&self) -> Self { 16 | Pair(self.0.clone(), self.1.clone()) 17 | } 18 | } 19 | 20 | #[derive(PartialEq, Eq, Hash)] 21 | struct BorrowedPair<'a, 'b, A: 'a, B: 'b>(&'a A, &'b B); 22 | 23 | trait KeyPair { 24 | /// Obtains the first element of the pair. 25 | fn a(&self) -> &A; 26 | /// Obtains the second element of the pair. 27 | fn b(&self) -> &B; 28 | } 29 | 30 | impl<'a, A, B> Borrow + 'a> for Pair 31 | where 32 | A: Eq + Hash + 'a, 33 | B: Eq + Hash + 'a, 34 | { 35 | fn borrow(&self) -> &(dyn KeyPair + 'a) { 36 | self 37 | } 38 | } 39 | 40 | impl<'a, A: Hash, B: Hash> Hash for (dyn KeyPair + 'a) { 41 | fn hash(&self, state: &mut H) { 42 | self.a().hash(state); 43 | self.b().hash(state); 44 | } 45 | } 46 | 47 | impl<'a, A: Eq, B: Eq> PartialEq for (dyn KeyPair + 'a) { 48 | fn eq(&self, other: &Self) -> bool { 49 | self.a() == other.a() && self.b() == other.b() 50 | } 51 | } 52 | 53 | impl<'a, A: Eq, B: Eq> Eq for (dyn KeyPair + 'a) {} 54 | 55 | // The main event 56 | pub struct DoubleKeyedMap { 57 | map: HashMap, V>, 58 | } 59 | 60 | // pass-through for selected HashMap methods. More can be added as needed. 61 | impl DoubleKeyedMap { 62 | pub fn new() -> Self { 63 | DoubleKeyedMap { 64 | map: HashMap::new(), 65 | } 66 | } 67 | 68 | pub fn get(&self, a: &A, b: &B) -> Option<&V> { 69 | self.map.get(&BorrowedPair(a, b) as &dyn KeyPair) 70 | } 71 | 72 | pub fn get_mut(&mut self, a: &A, b: &B) -> Option<&mut V> { 73 | self.map.get_mut(&BorrowedPair(a, b) as &dyn KeyPair) 74 | } 75 | 76 | pub fn insert(&mut self, a: A, b: B, v: V) { 77 | self.map.insert(Pair(a, b), v); 78 | } 79 | 80 | pub fn remove(&mut self, a: &A, b: &B) -> Option { 81 | self.map.remove(&BorrowedPair(a, b) as &dyn KeyPair) 82 | } 83 | 84 | pub fn is_empty(&self) -> bool { 85 | self.map.is_empty() 86 | } 87 | 88 | pub fn len(&self) -> usize { 89 | self.map.len() 90 | } 91 | 92 | // for now we just expose `Pair` in this method 93 | pub fn entry(&mut self, a: A, b: B) -> std::collections::hash_map::Entry, V> { 94 | self.map.entry(Pair(a, b)) 95 | } 96 | 97 | pub fn iter(&self) -> impl Iterator { 98 | self.map.iter().map(|(Pair(a, b), v)| (a, b, v)) 99 | } 100 | 101 | pub fn keys(&self) -> impl Iterator { 102 | self.map.keys().map(|Pair(a, b)| (a, b)) 103 | } 104 | 105 | pub fn values(&self) -> impl Iterator { 106 | self.map.values() 107 | } 108 | 109 | pub fn values_mut(&mut self) -> impl Iterator { 110 | self.map.values_mut() 111 | } 112 | } 113 | 114 | impl Clone for DoubleKeyedMap { 115 | fn clone(&self) -> Self { 116 | Self { 117 | map: self.map.clone(), 118 | } 119 | } 120 | } 121 | 122 | impl KeyPair for Pair 123 | where 124 | A: Eq + Hash, 125 | B: Eq + Hash, 126 | { 127 | fn a(&self) -> &A { 128 | &self.0 129 | } 130 | fn b(&self) -> &B { 131 | &self.1 132 | } 133 | } 134 | 135 | impl<'a, 'b, A, B> KeyPair for BorrowedPair<'a, 'b, A, B> 136 | where 137 | A: Eq + Hash + 'a, 138 | B: Eq + Hash + 'a, 139 | { 140 | fn a(&self) -> &A { 141 | self.0 142 | } 143 | fn b(&self) -> &B { 144 | self.1 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /src/hooks/allocation.rs: -------------------------------------------------------------------------------- 1 | //! Default hooks for malloc-related functions 2 | 3 | use crate::alloc_utils; 4 | use crate::backend::Backend; 5 | use crate::error::*; 6 | use crate::function_hooks::IsCall; 7 | use crate::return_value::*; 8 | use crate::state::State; 9 | use llvm_ir::*; 10 | 11 | pub fn malloc_hook<'p, B: Backend + 'p>( 12 | state: &mut State<'p, B>, 13 | call: &'p dyn IsCall, 14 | ) -> Result> { 15 | assert_eq!(call.get_arguments().len(), 1); 16 | let bytes = &call.get_arguments()[0].0; 17 | match state.type_of(bytes).as_ref() { 18 | Type::IntegerType { .. } => {}, 19 | ty => { 20 | return Err(Error::OtherError(format!( 21 | "malloc_hook: expected argument to have integer type, but got {:?}", 22 | ty 23 | ))) 24 | }, 25 | }; 26 | match state.type_of(call).as_ref() { 27 | Type::PointerType { .. } => {}, 28 | ty => { 29 | return Err(Error::OtherError(format!( 30 | "malloc_hook: expected return type to be a pointer type, but got {:?}", 31 | ty 32 | ))) 33 | }, 34 | }; 35 | 36 | let addr = alloc_utils::malloc(state, bytes)?; 37 | Ok(ReturnValue::Return(addr)) 38 | } 39 | 40 | pub fn calloc_hook<'p, B: Backend + 'p>( 41 | state: &mut State<'p, B>, 42 | call: &'p dyn IsCall, 43 | ) -> Result> { 44 | assert_eq!(call.get_arguments().len(), 2); 45 | let num = &call.get_arguments()[0].0; 46 | let size = &call.get_arguments()[1].0; 47 | match state.type_of(num).as_ref() { 48 | Type::IntegerType { .. } => {}, 49 | ty => { 50 | return Err(Error::OtherError(format!( 51 | "calloc_hook: expected first argument to have integer type, but got {:?}", 52 | ty 53 | ))) 54 | }, 55 | }; 56 | match state.type_of(size).as_ref() { 57 | Type::IntegerType { .. } => {}, 58 | ty => { 59 | return Err(Error::OtherError(format!( 60 | "calloc_hook: expected second argument to have integer type, but got {:?}", 61 | ty 62 | ))) 63 | }, 64 | }; 65 | match state.type_of(call).as_ref() { 66 | Type::PointerType { .. } => {}, 67 | ty => { 68 | return Err(Error::OtherError(format!( 69 | "calloc_hook: expected return type to be a pointer type, but got {:?}", 70 | ty 71 | ))) 72 | }, 73 | }; 74 | 75 | let addr = alloc_utils::calloc(state, num, size)?; 76 | Ok(ReturnValue::Return(addr)) 77 | } 78 | 79 | pub fn free_hook<'p, B: Backend + 'p>( 80 | _state: &mut State<'p, B>, 81 | _call: &'p dyn IsCall, 82 | ) -> Result> { 83 | // The simplest implementation of free() is a no-op. 84 | // Our allocator won't ever reuse allocated addresses anyway. 85 | Ok(ReturnValue::ReturnVoid) 86 | } 87 | 88 | pub fn realloc_hook<'p, B: Backend + 'p>( 89 | state: &mut State<'p, B>, 90 | call: &'p dyn IsCall, 91 | ) -> Result> { 92 | assert_eq!(call.get_arguments().len(), 2); 93 | let addr = &call.get_arguments()[0].0; 94 | let new_size = &call.get_arguments()[1].0; 95 | match state.type_of(addr).as_ref() { 96 | Type::PointerType { .. } => {}, 97 | ty => { 98 | return Err(Error::OtherError(format!( 99 | "realloc_hook: expected first argument to be a pointer type, but got {:?}", 100 | ty 101 | ))) 102 | }, 103 | }; 104 | match state.type_of(new_size).as_ref() { 105 | Type::IntegerType { .. } => {}, 106 | ty => { 107 | return Err(Error::OtherError(format!( 108 | "realloc_hook: expected second argument to be an integer type, but got {:?}", 109 | ty 110 | ))) 111 | }, 112 | }; 113 | match state.type_of(call).as_ref() { 114 | Type::PointerType { .. } => {}, 115 | ty => { 116 | return Err(Error::OtherError(format!( 117 | "realloc_hook: expected return type to be a pointer type, but got {:?}", 118 | ty 119 | ))) 120 | }, 121 | }; 122 | 123 | let addr = alloc_utils::realloc(state, addr, new_size)?; 124 | Ok(ReturnValue::Return(addr)) 125 | } 126 | -------------------------------------------------------------------------------- /tests/bcfiles/globals_initialization_1.ll: -------------------------------------------------------------------------------- 1 | ; ModuleID = 'globals_initialization_1.c' 2 | source_filename = "globals_initialization_1.c" 3 | target datalayout = "e-m:o-i64:64-f80:128-n8:16:32:64-S128" 4 | target triple = "x86_64-apple-macosx10.15.0" 5 | 6 | %struct.SomeStruct = type { i32, i32, i32 } 7 | %struct.StructWithPointers = type { i32, i32*, %struct.SomeStruct*, %struct.StructWithPointers* } 8 | %struct.StructWithFunctionPointer = type { i32, i32 (i32, i32)*, i8* } 9 | 10 | @a = local_unnamed_addr constant i32 2, align 4 11 | @b = local_unnamed_addr constant i32 2, align 4 12 | @c = local_unnamed_addr constant i32 6, align 4 13 | @ss0 = local_unnamed_addr constant %struct.SomeStruct zeroinitializer, align 4 14 | @ss1 = constant %struct.SomeStruct { i32 2, i32 6, i32 2 }, align 4 15 | @x = external constant i32, align 4 16 | @swp1 = constant %struct.StructWithPointers { i32 6, i32* getelementptr inbounds (%struct.StructWithPointers, %struct.StructWithPointers* @swp0, i32 0, i32 0), %struct.SomeStruct* @ss2, %struct.StructWithPointers* @swp0 }, align 8 17 | @swp0 = constant %struct.StructWithPointers { i32 2, i32* @x, %struct.SomeStruct* @ss1, %struct.StructWithPointers* @swp1 }, align 8 18 | @ss2 = external constant %struct.SomeStruct, align 4 19 | @crossMod1 = external constant %struct.StructWithPointers, align 8 20 | @crossMod0 = local_unnamed_addr constant %struct.StructWithPointers { i32 2, i32* getelementptr inbounds (%struct.StructWithPointers, %struct.StructWithPointers* @crossMod1, i32 0, i32 0), %struct.SomeStruct* @ss1, %struct.StructWithPointers* @crossMod1 }, align 8 21 | @swfp1 = local_unnamed_addr constant %struct.StructWithFunctionPointer { i32 21, i32 (i32, i32)* @bar, i8* bitcast (i32 ()* @foo to i8*) }, align 8 22 | 23 | ; Function Attrs: norecurse nounwind readnone ssp uwtable 24 | define i32 @bar(i32, i32) #0 { 25 | %3 = add nsw i32 %1, %0 26 | ret i32 %3 27 | } 28 | 29 | ; Function Attrs: norecurse nounwind readonly ssp uwtable 30 | define i32 @foo() #1 { 31 | %1 = load i32, i32* getelementptr inbounds (%struct.SomeStruct, %struct.SomeStruct* @ss2, i64 0, i32 2), align 4, !tbaa !3 32 | %2 = load i32, i32* @x, align 4, !tbaa !8 33 | %3 = load i32, i32* getelementptr inbounds (%struct.SomeStruct, %struct.SomeStruct* @ss2, i64 0, i32 1), align 4, !tbaa !9 34 | %4 = load %struct.StructWithPointers*, %struct.StructWithPointers** getelementptr inbounds (%struct.StructWithPointers, %struct.StructWithPointers* @crossMod1, i64 0, i32 3), align 8, !tbaa !10 35 | %5 = getelementptr inbounds %struct.StructWithPointers, %struct.StructWithPointers* %4, i64 0, i32 1 36 | %6 = load i32*, i32** %5, align 8, !tbaa !13 37 | %7 = load i32, i32* %6, align 4, !tbaa !8 38 | %8 = add i32 %1, 23 39 | %9 = add i32 %8, %2 40 | %10 = add i32 %9, %3 41 | %11 = add i32 %10, %7 42 | ret i32 %11 43 | } 44 | 45 | attributes #0 = { norecurse nounwind readnone ssp uwtable "correctly-rounded-divide-sqrt-fp-math"="false" "disable-tail-calls"="false" "less-precise-fpmad"="false" "min-legal-vector-width"="0" "no-frame-pointer-elim"="true" "no-frame-pointer-elim-non-leaf" "no-infs-fp-math"="false" "no-jump-tables"="false" "no-nans-fp-math"="false" "no-signed-zeros-fp-math"="false" "no-trapping-math"="false" "stack-protector-buffer-size"="8" "target-cpu"="penryn" "target-features"="+cx16,+cx8,+fxsr,+mmx,+sahf,+sse,+sse2,+sse3,+sse4.1,+ssse3,+x87" "unsafe-fp-math"="false" "use-soft-float"="false" } 46 | attributes #1 = { norecurse nounwind readonly ssp uwtable "correctly-rounded-divide-sqrt-fp-math"="false" "disable-tail-calls"="false" "less-precise-fpmad"="false" "min-legal-vector-width"="0" "no-frame-pointer-elim"="true" "no-frame-pointer-elim-non-leaf" "no-infs-fp-math"="false" "no-jump-tables"="false" "no-nans-fp-math"="false" "no-signed-zeros-fp-math"="false" "no-trapping-math"="false" "stack-protector-buffer-size"="8" "target-cpu"="penryn" "target-features"="+cx16,+cx8,+fxsr,+mmx,+sahf,+sse,+sse2,+sse3,+sse4.1,+ssse3,+x87" "unsafe-fp-math"="false" "use-soft-float"="false" } 47 | 48 | !llvm.module.flags = !{!0, !1} 49 | !llvm.ident = !{!2} 50 | 51 | !0 = !{i32 1, !"wchar_size", i32 4} 52 | !1 = !{i32 7, !"PIC Level", i32 2} 53 | !2 = !{!"clang version 9.0.1 "} 54 | !3 = !{!4, !5, i64 8} 55 | !4 = !{!"SomeStruct", !5, i64 0, !5, i64 4, !5, i64 8} 56 | !5 = !{!"int", !6, i64 0} 57 | !6 = !{!"omnipotent char", !7, i64 0} 58 | !7 = !{!"Simple C/C++ TBAA"} 59 | !8 = !{!5, !5, i64 0} 60 | !9 = !{!4, !5, i64 4} 61 | !10 = !{!11, !12, i64 24} 62 | !11 = !{!"StructWithPointers", !5, i64 0, !12, i64 8, !12, i64 16, !12, i64 24} 63 | !12 = !{!"any pointer", !6, i64 0} 64 | !13 = !{!11, !12, i64 8} 65 | -------------------------------------------------------------------------------- /tests/bcfiles/crossmod.ll: -------------------------------------------------------------------------------- 1 | ; ModuleID = 'crossmod.c' 2 | source_filename = "crossmod.c" 3 | target datalayout = "e-m:o-i64:64-f80:128-n8:16:32:64-S128" 4 | target triple = "x86_64-apple-macosx10.15.0" 5 | 6 | @global1 = external global i32, align 4 7 | @global3 = external global i32, align 4 8 | 9 | ; Function Attrs: noinline nounwind ssp uwtable 10 | define i32 @cross_module_simple_caller(i32) local_unnamed_addr #0 { 11 | %2 = tail call i32 @simple_callee(i32 %0, i32 3) #4 12 | ret i32 %2 13 | } 14 | 15 | declare i32 @simple_callee(i32, i32) local_unnamed_addr #1 16 | 17 | ; Function Attrs: nounwind ssp uwtable 18 | define i32 @cross_module_twice_caller(i32) local_unnamed_addr #2 { 19 | %2 = tail call i32 @simple_callee(i32 %0, i32 5) #4 20 | %3 = tail call i32 @simple_callee(i32 %0, i32 1) #4 21 | %4 = add nsw i32 %3, %2 22 | ret i32 %4 23 | } 24 | 25 | ; Function Attrs: nounwind ssp uwtable 26 | define i32 @cross_module_nested_near_caller(i32, i32) local_unnamed_addr #2 { 27 | %3 = add nsw i32 %1, %0 28 | %4 = tail call i32 @cross_module_simple_caller(i32 %3) 29 | ret i32 %4 30 | } 31 | 32 | ; Function Attrs: nounwind ssp uwtable 33 | define i32 @cross_module_nested_far_caller(i32, i32) local_unnamed_addr #2 { 34 | %3 = add nsw i32 %1, %0 35 | %4 = tail call i32 @simple_caller(i32 %3) #4 36 | ret i32 %4 37 | } 38 | 39 | declare i32 @simple_caller(i32) local_unnamed_addr #1 40 | 41 | ; Function Attrs: nofree norecurse nounwind ssp uwtable 42 | define i32 @cross_module_read_global() local_unnamed_addr #3 { 43 | %1 = load volatile i32, i32* @global1, align 4, !tbaa !3 44 | ret i32 %1 45 | } 46 | 47 | ; Function Attrs: nounwind ssp uwtable 48 | define i32 @cross_module_read_global_via_call() local_unnamed_addr #2 { 49 | %1 = tail call i32 (...) @read_global() #4 50 | ret i32 %1 51 | } 52 | 53 | declare i32 @read_global(...) local_unnamed_addr #1 54 | 55 | ; Function Attrs: nofree norecurse nounwind ssp uwtable 56 | define i32 @cross_module_modify_global(i32) local_unnamed_addr #3 { 57 | store volatile i32 %0, i32* @global3, align 4, !tbaa !3 58 | %2 = load volatile i32, i32* @global3, align 4, !tbaa !3 59 | ret i32 %2 60 | } 61 | 62 | ; Function Attrs: nounwind ssp uwtable 63 | define i32 @cross_module_modify_global_via_call(i32) local_unnamed_addr #2 { 64 | %2 = tail call i32 @modify_global(i32 %0) #4 65 | %3 = load volatile i32, i32* @global3, align 4, !tbaa !3 66 | ret i32 %3 67 | } 68 | 69 | declare i32 @modify_global(i32) local_unnamed_addr #1 70 | 71 | attributes #0 = { noinline nounwind ssp uwtable "correctly-rounded-divide-sqrt-fp-math"="false" "disable-tail-calls"="false" "less-precise-fpmad"="false" "min-legal-vector-width"="0" "no-frame-pointer-elim"="true" "no-frame-pointer-elim-non-leaf" "no-infs-fp-math"="false" "no-jump-tables"="false" "no-nans-fp-math"="false" "no-signed-zeros-fp-math"="false" "no-trapping-math"="false" "stack-protector-buffer-size"="8" "target-cpu"="penryn" "target-features"="+cx16,+cx8,+fxsr,+mmx,+sahf,+sse,+sse2,+sse3,+sse4.1,+ssse3,+x87" "unsafe-fp-math"="false" "use-soft-float"="false" } 72 | attributes #1 = { "correctly-rounded-divide-sqrt-fp-math"="false" "disable-tail-calls"="false" "less-precise-fpmad"="false" "no-frame-pointer-elim"="true" "no-frame-pointer-elim-non-leaf" "no-infs-fp-math"="false" "no-nans-fp-math"="false" "no-signed-zeros-fp-math"="false" "no-trapping-math"="false" "stack-protector-buffer-size"="8" "target-cpu"="penryn" "target-features"="+cx16,+cx8,+fxsr,+mmx,+sahf,+sse,+sse2,+sse3,+sse4.1,+ssse3,+x87" "unsafe-fp-math"="false" "use-soft-float"="false" } 73 | attributes #2 = { nounwind ssp uwtable "correctly-rounded-divide-sqrt-fp-math"="false" "disable-tail-calls"="false" "less-precise-fpmad"="false" "min-legal-vector-width"="0" "no-frame-pointer-elim"="true" "no-frame-pointer-elim-non-leaf" "no-infs-fp-math"="false" "no-jump-tables"="false" "no-nans-fp-math"="false" "no-signed-zeros-fp-math"="false" "no-trapping-math"="false" "stack-protector-buffer-size"="8" "target-cpu"="penryn" "target-features"="+cx16,+cx8,+fxsr,+mmx,+sahf,+sse,+sse2,+sse3,+sse4.1,+ssse3,+x87" "unsafe-fp-math"="false" "use-soft-float"="false" } 74 | attributes #3 = { nofree norecurse nounwind ssp uwtable "correctly-rounded-divide-sqrt-fp-math"="false" "disable-tail-calls"="false" "less-precise-fpmad"="false" "min-legal-vector-width"="0" "no-frame-pointer-elim"="true" "no-frame-pointer-elim-non-leaf" "no-infs-fp-math"="false" "no-jump-tables"="false" "no-nans-fp-math"="false" "no-signed-zeros-fp-math"="false" "no-trapping-math"="false" "stack-protector-buffer-size"="8" "target-cpu"="penryn" "target-features"="+cx16,+cx8,+fxsr,+mmx,+sahf,+sse,+sse2,+sse3,+sse4.1,+ssse3,+x87" "unsafe-fp-math"="false" "use-soft-float"="false" } 75 | attributes #4 = { nounwind } 76 | 77 | !llvm.module.flags = !{!0, !1} 78 | !llvm.ident = !{!2} 79 | 80 | !0 = !{i32 1, !"wchar_size", i32 4} 81 | !1 = !{i32 7, !"PIC Level", i32 2} 82 | !2 = !{!"clang version 9.0.1 "} 83 | !3 = !{!4, !4, i64 0} 84 | !4 = !{!"int", !5, i64 0} 85 | !5 = !{!"omnipotent char", !6, i64 0} 86 | !6 = !{!"Simple C/C++ TBAA"} 87 | -------------------------------------------------------------------------------- /src/error.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | 3 | /// Error types used throughout this crate. 4 | /// 5 | /// The `Display` impl for `Error` will provide information about the error 6 | /// itself. For more detailed information about the error, including the program 7 | /// context in which it occurred, see 8 | /// [`State.full_error_message_with_context()`](struct.State.html#method.full_error_message_with_context). 9 | #[derive(PartialEq, Eq, Clone, Debug)] 10 | pub enum Error { 11 | /// While performing an operation, we discovered the current path is unsat. 12 | /// 13 | /// This error type is used internally, but (by default) isn't exposed to consumers of `ExecutionManager`; 14 | /// see [`Config.squash_unsats`](config/struct.Config.html#structfield.squash_unsats). 15 | Unsat, 16 | /// The current path has exceeded the configured `loop_bound` (see [`Config`](config/struct.Config.html)). 17 | /// (The `usize` here indicates the value of the configured `loop_bound`.) 18 | LoopBoundExceeded(usize), 19 | /// The current path has attempted to dereference a null pointer (or 20 | /// more precisely, a pointer for which `NULL` is a possible value) 21 | NullPointerDereference, 22 | /// Processing a call of a function with the given name, but failed to find an LLVM definition, a function hook, or a built-in handler for it 23 | FunctionNotFound(String), 24 | /// The solver returned this processing error while evaluating a query. 25 | /// Often, this is a timeout; see [`Config.solver_query_timeout`](config/struct.Config.html#structfield.solver_query_timeout) 26 | SolverError(String), 27 | /// Encountered an LLVM instruction which is not currently supported 28 | UnsupportedInstruction(String), 29 | /// Encountered an LLVM instruction which was malformed, or at least didn't conform to our expected invariants 30 | MalformedInstruction(String), 31 | /// Reached an LLVM `Unreachable` instruction 32 | UnreachableInstruction, 33 | /// Failed to interpret some symbolic value (`BV`) as a function pointer, 34 | /// because it has a possible solution (the `u64` here) which points to 35 | /// something that's not a function 36 | FailedToResolveFunctionPointer(u64), 37 | /// The hook for some function returned a value which didn't match the 38 | /// function return type: for instance, a value of the wrong size. 39 | /// The `String` here just describes the error 40 | HookReturnValueMismatch(String), 41 | /// Some kind of error which doesn't fall into one of the above categories. 42 | /// The `String` here describes the error 43 | OtherError(String), 44 | } 45 | 46 | impl fmt::Display for Error { 47 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 48 | match self { 49 | Error::Unsat => 50 | write!(f, "`Unsat`: the current state or path is unsat"), 51 | Error::LoopBoundExceeded(bound) => 52 | write!(f, "`LoopBoundExceeded`: the current path has exceeded the configured `loop_bound`, which was {}", bound), 53 | Error::NullPointerDereference => 54 | write!(f, "`NullPointerDereference`: the current path has attempted to dereference a null pointer"), 55 | Error::FunctionNotFound(funcname) => 56 | write!(f, "`FunctionNotFound`: encountered a call of a function named {:?}, but failed to find an LLVM definition, a function hook, or a built-in handler for it", funcname), 57 | Error::SolverError(details) => 58 | write!(f, "`SolverError`: the solver returned this error while evaluating a query: {}", details), 59 | Error::UnsupportedInstruction(details) => 60 | write!(f, "`UnsupportedInstruction`: encountered an LLVM instruction which is not currently supported: {}", details), 61 | Error::MalformedInstruction(details) => 62 | write!(f, "`MalformedInstruction`: encountered an LLVM instruction which was malformed, or at least didn't conform to our expected invariants: {}", details), 63 | Error::UnreachableInstruction => 64 | write!(f, "`UnreachableInstruction`: Reached an LLVM 'Unreachable' instruction"), 65 | Error::FailedToResolveFunctionPointer(solution) => 66 | write!(f, "`FailedToResolveFunctionPointer`: Can't resolve a symbolically-valued function pointer, because one possible solution for it ({:#x}) points to something that's not a function", solution), 67 | Error::HookReturnValueMismatch(details) => 68 | write!(f, "`HookReturnValueMismatch`: {}", details), 69 | Error::OtherError(details) => 70 | write!(f, "`OtherError`: {}", details), 71 | } 72 | } 73 | } 74 | 75 | impl From for String { 76 | fn from(e: Error) -> String { 77 | e.to_string() // use the Display impl 78 | } 79 | } 80 | 81 | /// A type alias for convenience, similar to how `std::io::Result` is used for I/O operations 82 | pub type Result = std::result::Result; 83 | -------------------------------------------------------------------------------- /tests/bcfiles/struct-O3.ll: -------------------------------------------------------------------------------- 1 | ; ModuleID = 'struct-O3.c' 2 | source_filename = "struct-O3.c" 3 | target datalayout = "e-m:o-i64:64-f80:128-n8:16:32:64-S128" 4 | target triple = "x86_64-apple-macosx10.15.0" 5 | 6 | %struct.WithPointer = type { %struct.TwoInts, %struct.TwoInts** } 7 | %struct.TwoInts = type { i32, i32 } 8 | 9 | ; Function Attrs: nofree noinline norecurse nounwind ssp uwtable 10 | define i32 @called(%struct.WithPointer*, i32) local_unnamed_addr #0 { 11 | %3 = add nsw i32 %1, -3 12 | %4 = getelementptr inbounds %struct.WithPointer, %struct.WithPointer* %0, i64 0, i32 0, i32 1 13 | store volatile i32 %3, i32* %4, align 4, !tbaa !3 14 | %5 = getelementptr inbounds %struct.WithPointer, %struct.WithPointer* %0, i64 0, i32 1 15 | %6 = load %struct.TwoInts**, %struct.TwoInts*** %5, align 8, !tbaa !10 16 | %7 = load volatile %struct.TwoInts*, %struct.TwoInts** %6, align 8, !tbaa !11 17 | %8 = getelementptr inbounds %struct.TwoInts, %struct.TwoInts* %7, i64 0, i32 1 18 | %9 = load volatile i32, i32* %8, align 4, !tbaa !12 19 | ret i32 %9 20 | } 21 | 22 | ; Function Attrs: nounwind ssp uwtable 23 | define i32 @with_ptr(i32) local_unnamed_addr #1 { 24 | %2 = alloca %struct.TwoInts*, align 8 25 | %3 = bitcast %struct.TwoInts** %2 to i8* 26 | call void @llvm.lifetime.start.p0i8(i64 8, i8* nonnull %3) #4 27 | %4 = tail call i8* @malloc(i64 8) #5 28 | %5 = bitcast %struct.TwoInts** %2 to i8** 29 | store i8* %4, i8** %5, align 8, !tbaa !11 30 | %6 = tail call i8* @malloc(i64 16) #5 31 | %7 = icmp eq i8* %6, null 32 | %8 = icmp eq i8* %4, null 33 | %9 = or i1 %8, %7 34 | br i1 %9, label %18, label %10 35 | 36 | 10: ; preds = %1 37 | %11 = bitcast i8* %6 to %struct.WithPointer* 38 | %12 = bitcast i8* %6 to %struct.TwoInts* 39 | %13 = getelementptr inbounds i8, i8* %6, i64 4 40 | %14 = bitcast i8* %13 to i32* 41 | store volatile i32 0, i32* %14, align 4, !tbaa !3 42 | %15 = getelementptr inbounds i8, i8* %6, i64 8 43 | %16 = bitcast i8* %15 to %struct.TwoInts*** 44 | store %struct.TwoInts** %2, %struct.TwoInts*** %16, align 8, !tbaa !10 45 | store volatile %struct.TwoInts* %12, %struct.TwoInts** %2, align 8, !tbaa !11 46 | %17 = call i32 @called(%struct.WithPointer* %11, i32 %0) 47 | br label %18 48 | 49 | 18: ; preds = %1, %10 50 | %19 = phi i32 [ %17, %10 ], [ -1, %1 ] 51 | call void @llvm.lifetime.end.p0i8(i64 8, i8* nonnull %3) #4 52 | ret i32 %19 53 | } 54 | 55 | ; Function Attrs: argmemonly nounwind 56 | declare void @llvm.lifetime.start.p0i8(i64 immarg, i8* nocapture) #2 57 | 58 | ; Function Attrs: nofree nounwind allocsize(0) 59 | declare noalias i8* @malloc(i64) local_unnamed_addr #3 60 | 61 | ; Function Attrs: argmemonly nounwind 62 | declare void @llvm.lifetime.end.p0i8(i64 immarg, i8* nocapture) #2 63 | 64 | attributes #0 = { nofree noinline norecurse nounwind ssp uwtable "correctly-rounded-divide-sqrt-fp-math"="false" "disable-tail-calls"="false" "less-precise-fpmad"="false" "min-legal-vector-width"="0" "no-frame-pointer-elim"="true" "no-frame-pointer-elim-non-leaf" "no-infs-fp-math"="false" "no-jump-tables"="false" "no-nans-fp-math"="false" "no-signed-zeros-fp-math"="false" "no-trapping-math"="false" "stack-protector-buffer-size"="8" "target-cpu"="penryn" "target-features"="+cx16,+cx8,+fxsr,+mmx,+sahf,+sse,+sse2,+sse3,+sse4.1,+ssse3,+x87" "unsafe-fp-math"="false" "use-soft-float"="false" } 65 | attributes #1 = { nounwind ssp uwtable "correctly-rounded-divide-sqrt-fp-math"="false" "disable-tail-calls"="false" "less-precise-fpmad"="false" "min-legal-vector-width"="0" "no-frame-pointer-elim"="true" "no-frame-pointer-elim-non-leaf" "no-infs-fp-math"="false" "no-jump-tables"="false" "no-nans-fp-math"="false" "no-signed-zeros-fp-math"="false" "no-trapping-math"="false" "stack-protector-buffer-size"="8" "target-cpu"="penryn" "target-features"="+cx16,+cx8,+fxsr,+mmx,+sahf,+sse,+sse2,+sse3,+sse4.1,+ssse3,+x87" "unsafe-fp-math"="false" "use-soft-float"="false" } 66 | attributes #2 = { argmemonly nounwind } 67 | attributes #3 = { nofree nounwind allocsize(0) "correctly-rounded-divide-sqrt-fp-math"="false" "disable-tail-calls"="false" "less-precise-fpmad"="false" "no-frame-pointer-elim"="true" "no-frame-pointer-elim-non-leaf" "no-infs-fp-math"="false" "no-nans-fp-math"="false" "no-signed-zeros-fp-math"="false" "no-trapping-math"="false" "stack-protector-buffer-size"="8" "target-cpu"="penryn" "target-features"="+cx16,+cx8,+fxsr,+mmx,+sahf,+sse,+sse2,+sse3,+sse4.1,+ssse3,+x87" "unsafe-fp-math"="false" "use-soft-float"="false" } 68 | attributes #4 = { nounwind } 69 | attributes #5 = { allocsize(0) } 70 | 71 | !llvm.module.flags = !{!0, !1} 72 | !llvm.ident = !{!2} 73 | 74 | !0 = !{i32 1, !"wchar_size", i32 4} 75 | !1 = !{i32 7, !"PIC Level", i32 2} 76 | !2 = !{!"clang version 9.0.1 "} 77 | !3 = !{!4, !6, i64 4} 78 | !4 = !{!"WithPointer", !5, i64 0, !9, i64 8} 79 | !5 = !{!"TwoInts", !6, i64 0, !6, i64 4} 80 | !6 = !{!"int", !7, i64 0} 81 | !7 = !{!"omnipotent char", !8, i64 0} 82 | !8 = !{!"Simple C/C++ TBAA"} 83 | !9 = !{!"any pointer", !7, i64 0} 84 | !10 = !{!4, !9, i64 8} 85 | !11 = !{!9, !9, i64 0} 86 | !12 = !{!5, !6, i64 4} 87 | -------------------------------------------------------------------------------- /src/alloc_utils.rs: -------------------------------------------------------------------------------- 1 | //! Utility functions for performing memory allocation. 2 | //! These may be useful in implementing hooks for various functions that 3 | //! perform memory allocation. 4 | 5 | use crate::backend::Backend; 6 | use crate::error::*; 7 | use crate::state::State; 8 | use llvm_ir::*; 9 | use log::warn; 10 | 11 | /// Assume that allocations never exceed this size. 12 | const MAX_ALLOCATION_SIZE_BYTES: u64 = 1 << 20; 13 | 14 | /// Allocate a number of bytes given by the `Operand`. 15 | /// 16 | /// Returns the address of the newly-allocated memory. 17 | pub fn malloc(state: &mut State, num_bytes: &Operand) -> Result { 18 | // Note that allocating too much doesn't hurt anything, as long as we don't 19 | // run out of address space in our symbolic memory. 20 | let num_bytes = try_as_u64(num_bytes).unwrap_or(MAX_ALLOCATION_SIZE_BYTES); 21 | if num_bytes > MAX_ALLOCATION_SIZE_BYTES { 22 | warn!("warning: encountered an allocation of {} bytes, greater than the assumed max of {}. \ 23 | Since this allocation is constant-sized, it's fine in this case, but does draw into question the assumption.", num_bytes, MAX_ALLOCATION_SIZE_BYTES); 24 | } 25 | let num_bits = num_bytes * 8; 26 | Ok(state.allocate(num_bits)) 27 | } 28 | 29 | /// Allocate a number of bytes given by the `Operand`. 30 | /// The newly-allocated memory will be initialized to all zeroes. 31 | /// 32 | /// Returns the address of the newly-allocated memory. 33 | pub fn zalloc(state: &mut State, num_bytes: &Operand) -> Result { 34 | // As in `malloc()`, note that allocating too much doesn't hurt anything 35 | let num_bytes = try_as_u64(num_bytes).unwrap_or(MAX_ALLOCATION_SIZE_BYTES); 36 | if num_bytes > MAX_ALLOCATION_SIZE_BYTES { 37 | warn!("warning: encountered an allocation of {} bytes, greater than the assumed max of {}. \ 38 | Since this allocation is constant-sized, it's fine in this case, but does draw into question the assumption.", num_bytes, MAX_ALLOCATION_SIZE_BYTES); 39 | } 40 | let num_bits = num_bytes * 8; 41 | let addr = state.allocate(num_bits); 42 | state.write(&addr, state.zero(num_bits as u32))?; 43 | Ok(addr) 44 | } 45 | 46 | /// Allocate a number of bytes given by `a` times `b`, where `a` and `b` are 47 | /// `Operand`s. The newly-allocated memory will be initialized to all zeroes. 48 | /// 49 | /// Returns the address of the newly-allocated memory. 50 | pub fn calloc(state: &mut State, a: &Operand, b: &Operand) -> Result { 51 | // As in `malloc()`, note that allocating too much doesn't hurt anything 52 | let num_bytes = match (try_as_u64(a), try_as_u64(b)) { 53 | (Some(a), Some(b)) => a * b, 54 | _ => MAX_ALLOCATION_SIZE_BYTES, 55 | }; 56 | if num_bytes > MAX_ALLOCATION_SIZE_BYTES { 57 | warn!("warning: encountered an allocation of {} bytes, greater than the assumed max of {}. \ 58 | Since this allocation is constant-sized, it's fine in this case, but does draw into question the assumption.", num_bytes, MAX_ALLOCATION_SIZE_BYTES); 59 | } 60 | let num_bits = num_bytes * 8; 61 | let addr = state.allocate(num_bits); 62 | state.write(&addr, state.zero(num_bits as u32))?; 63 | Ok(addr) 64 | } 65 | 66 | /// Reallocate the given `addr` to be at least the number of bytes given by the `Operand`. 67 | /// 68 | /// Returns the address of the allocation, which may or may not be the same 69 | /// address which was passed in. 70 | pub fn realloc( 71 | state: &mut State, 72 | addr: &Operand, 73 | num_bytes: &Operand, 74 | ) -> Result { 75 | let addr = state.operand_to_bv(addr)?; 76 | // As in `malloc()`, note that allocating too much doesn't hurt anything 77 | let new_size = try_as_u64(num_bytes).unwrap_or(MAX_ALLOCATION_SIZE_BYTES); 78 | if new_size > MAX_ALLOCATION_SIZE_BYTES { 79 | warn!("warning: encountered an allocation of {} bytes, greater than the assumed max of {}. \ 80 | Since this allocation is constant-sized, it's fine in this case, but does draw into question the assumption.", new_size, MAX_ALLOCATION_SIZE_BYTES); 81 | } 82 | let old_size = state.get_allocation_size(&addr)?.ok_or_else(|| { 83 | Error::OtherError("realloc: failed to get old allocation size".to_owned()) 84 | })?; 85 | if new_size <= old_size { 86 | // We treat this as a no-op. You get to keep the larger old_size region you already had. 87 | Ok(addr) 88 | } else { 89 | // Make a new allocation 90 | let new_addr = state.allocate(new_size); 91 | // Copy the contents of the old allocation 92 | let contents = state.read(&addr, old_size as u32)?; 93 | state.write(&new_addr, contents)?; 94 | // We don't free(), as our allocator won't ever reuse allocated addresses anyway. 95 | // So, we can just return 96 | Ok(new_addr) 97 | } 98 | } 99 | 100 | /// Try to interpret the `Operand` as a constant integer, and if so, return the value as a `u64`. 101 | /// (But don't try too hard - as of this writing, doesn't even try to evaluate constant expressions.) 102 | fn try_as_u64(op: &Operand) -> Option { 103 | match op { 104 | Operand::ConstantOperand(cref) => match cref.as_ref() { 105 | Constant::Int { value, .. } => Some(*value), 106 | _ => None, 107 | }, 108 | _ => None, 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /tests/bcfiles/struct.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | struct OneInt { 4 | int el1; 5 | }; 6 | 7 | struct TwoInts { 8 | int el1; 9 | int el2; 10 | }; 11 | 12 | struct ThreeInts { 13 | int el1; 14 | int el2; 15 | int el3; 16 | }; 17 | 18 | struct Mismatched { 19 | uint8_t el1; 20 | uint32_t el2; 21 | uint8_t el3; 22 | }; 23 | 24 | struct Nested { 25 | struct TwoInts ti; 26 | struct Mismatched mm; 27 | }; 28 | 29 | struct WithArray { 30 | struct Mismatched mm; 31 | int arr[10]; 32 | struct Mismatched mm2; 33 | }; 34 | 35 | // read and write from OneInt 36 | int one_int(int x) { 37 | volatile struct OneInt oi = { 0 }; 38 | oi.el1 = x; 39 | return oi.el1 - 3; 40 | } 41 | 42 | // read and write from first field in TwoInts 43 | int two_ints_first(int x) { 44 | volatile struct TwoInts ti = { 0 }; 45 | ti.el1 = x; 46 | return ti.el1 - 3; 47 | } 48 | 49 | // read and write from second field in TwoInts 50 | int two_ints_second(int x) { 51 | volatile struct TwoInts ti = { 0 }; 52 | ti.el2 = x; 53 | return ti.el2 - 3; 54 | } 55 | 56 | // read and write from both TwoInts fields without getting them confused 57 | int two_ints_both(int x) { 58 | volatile struct TwoInts ti = { 0 }; 59 | ti.el1 = x + 2; 60 | ti.el2 = x + 3; 61 | ti.el1 = ti.el2 - 10; 62 | ti.el2 = ti.el1 + 7; 63 | return ti.el2 - 3; 64 | } 65 | 66 | // read and write from all fields in ThreeInts without getting them confused 67 | int three_ints(int x, int y) { 68 | volatile struct ThreeInts ti = { 0 }; 69 | ti.el1 = x + y; 70 | ti.el2 = x - y; 71 | ti.el3 = ti.el1 + ti.el2; 72 | ti.el2 = ti.el3 - 2 * ti.el1; 73 | ti.el1 = ti.el3 - x; 74 | return ti.el1 - 3; 75 | } 76 | 77 | // ensure that zero-initializing a struct works properly 78 | int zero_initialize(int x) { 79 | volatile struct ThreeInts ti = { 0 }; 80 | int a = ti.el1 + 2; 81 | int b = ti.el2 + 4; 82 | int c = ti.el3 + 6; 83 | ti.el2 = a + b + c; 84 | return x - ti.el2; 85 | } 86 | 87 | // ensure that non-zero-initializing a struct works properly 88 | int nonzero_initialize(int x) { 89 | volatile struct ThreeInts ti = { 1, 3, 87 }; 90 | int a = ti.el1 + 2; 91 | int b = ti.el2 + 4; 92 | int c = ti.el3 + 6; 93 | ti.el2 = a + b + c; 94 | return x - ti.el2; 95 | } 96 | 97 | // read and write from the first field in Mismatched 98 | uint8_t mismatched_first(uint8_t x) { 99 | volatile struct Mismatched mm = { 0 }; 100 | mm.el1 = x; 101 | return mm.el1 - 3; 102 | } 103 | 104 | // read and write from the second field in Mismatched 105 | int mismatched_second(int x) { 106 | volatile struct Mismatched mm = { 0 }; 107 | mm.el2 = x; 108 | return mm.el2 - 3; 109 | } 110 | 111 | // read and write from the third field in Mismatched 112 | uint8_t mismatched_third(uint8_t x) { 113 | volatile struct Mismatched mm = { 0 }; 114 | mm.el3 = x; 115 | return mm.el3 - 3; 116 | } 117 | 118 | // read and write from all fields in Mismatched without getting them confused 119 | int mismatched_all(uint8_t x, int y) { 120 | volatile struct Mismatched mm = { 0 }; 121 | mm.el1 = x + 3; 122 | mm.el2 = y - 3; 123 | mm.el3 = mm.el1 - x; 124 | mm.el1 = mm.el3 - x; 125 | mm.el2 = mm.el2 + 4; 126 | mm.el1 = mm.el1 - x; 127 | mm.el3 = mm.el3 - 5; 128 | mm.el2 = mm.el2 + y; 129 | return mm.el1 + mm.el2 + mm.el3; 130 | } 131 | 132 | // read and write from the first struct in Nested 133 | int nested_first(int x) { 134 | volatile struct Nested n = { 0 }; 135 | n.ti.el1 = x; 136 | n.ti.el2 = 3; 137 | return n.ti.el1 - n.ti.el2; 138 | } 139 | 140 | // read and write from the second struct in Nested 141 | int nested_second(int x) { 142 | volatile struct Nested n = { 0 }; 143 | n.mm.el2 = x; 144 | return n.mm.el2 - 3; 145 | } 146 | 147 | // read and write from all fields in Nested without getting them confused 148 | int nested_all(uint8_t x, int y) { 149 | volatile struct Nested n = { 0 }; 150 | n.ti.el2 = y + 3; 151 | n.mm.el1 = x - 4; 152 | n.ti.el1 = n.mm.el2 + y; 153 | n.mm.el3 = n.mm.el1 + 10; 154 | n.mm.el2 = n.mm.el3 + n.mm.el1; 155 | n.ti.el2 = n.mm.el3 + n.ti.el1; 156 | return n.ti.el2 - y; 157 | } 158 | 159 | // read and write from the array field in WithArray 160 | int with_array(int x) { 161 | volatile struct WithArray wa = { 0 }; 162 | wa.arr[4] = x; 163 | wa.arr[7] = 3; 164 | return wa.arr[4] - wa.arr[7]; 165 | } 166 | 167 | // read and write from all fields in WithArray without getting them confused 168 | int with_array_all(int x) { 169 | volatile struct WithArray wa = { 0 }; 170 | wa.arr[2] = x - 4; 171 | wa.arr[4] = wa.arr[5] - 3; 172 | wa.mm.el2 = wa.arr[2]; 173 | wa.mm2.el2 = wa.arr[2] + x + 1; 174 | return wa.arr[4] + wa.mm2.el2; 175 | } 176 | 177 | // manipulate a struct through a pointer 178 | int structptr(int x) { 179 | volatile struct TwoInts _ti = { 0 }; 180 | volatile struct TwoInts* ti = &_ti; 181 | ti->el2 = x - 6; 182 | ti->el1 = ti->el2 + x; 183 | ti->el2 = 100; 184 | return ti->el1; 185 | } 186 | 187 | // pointer to a particular element of a struct 188 | int structelptr(int x) { 189 | volatile struct ThreeInts _ti = { 0 }; 190 | volatile struct ThreeInts* ti = &_ti; 191 | volatile int* iptr = &ti->el2; 192 | *iptr = 3; 193 | *iptr = x - *iptr; 194 | return *iptr; 195 | } 196 | 197 | // change the target of a pointer 198 | int changeptr(int x) { 199 | volatile struct ThreeInts _ti1 = { 0 }; 200 | volatile struct ThreeInts _ti2 = { 0 }; 201 | volatile struct ThreeInts* volatile ti = &_ti1; 202 | ti->el2 = 7; 203 | ti = &_ti2; 204 | ti->el2 = x - 3 - ti->el2; 205 | ti = &_ti1; 206 | ti->el2 = 100; 207 | return _ti2.el2; 208 | } 209 | -------------------------------------------------------------------------------- /tests/global_tests.rs: -------------------------------------------------------------------------------- 1 | use haybale::solver_utils::PossibleSolutions; 2 | use haybale::*; 3 | 4 | fn init_logging() { 5 | // capture log messages with test harness 6 | let _ = env_logger::builder().is_test(true).try_init(); 7 | } 8 | 9 | fn get_project() -> Project { 10 | let modname = "tests/bcfiles/globals.bc"; 11 | Project::from_bc_path(modname) 12 | .unwrap_or_else(|e| panic!("Failed to parse module {:?}: {}", modname, e)) 13 | } 14 | 15 | fn get_cross_module_project() -> Project { 16 | Project::from_bc_paths(&["tests/bcfiles/globals.bc", "tests/bcfiles/crossmod.bc"]) 17 | .unwrap_or_else(|e| panic!("Failed to parse modules: {}", e)) 18 | } 19 | 20 | #[test] 21 | fn read_global() { 22 | let funcname = "read_global"; 23 | init_logging(); 24 | let proj = get_project(); 25 | assert_eq!( 26 | get_possible_return_values_of_func( 27 | funcname, 28 | &proj, 29 | Config::default(), 30 | Some(vec![]), 31 | None, 32 | 5 33 | ), 34 | PossibleSolutions::exactly_one(ReturnValue::Return(3)), 35 | ); 36 | } 37 | 38 | #[test] 39 | fn modify_global() { 40 | let funcname = "modify_global"; 41 | init_logging(); 42 | let proj = get_project(); 43 | assert_eq!( 44 | get_possible_return_values_of_func( 45 | funcname, 46 | &proj, 47 | Config::default(), 48 | Some(vec!(ParameterVal::ExactValue(3))), 49 | None, 50 | 5 51 | ), 52 | PossibleSolutions::exactly_one(ReturnValue::Return(3)), 53 | ) 54 | } 55 | 56 | #[test] 57 | fn modify_global_with_call() { 58 | let funcname = "modify_global_with_call"; 59 | init_logging(); 60 | let proj = get_project(); 61 | assert_eq!( 62 | get_possible_return_values_of_func( 63 | funcname, 64 | &proj, 65 | Config::default(), 66 | Some(vec!(ParameterVal::ExactValue(3))), 67 | None, 68 | 5 69 | ), 70 | PossibleSolutions::exactly_one(ReturnValue::Return(3)), 71 | ) 72 | } 73 | 74 | #[test] 75 | fn dont_confuse_globals() { 76 | let funcname = "dont_confuse_globals"; 77 | init_logging(); 78 | let proj = get_project(); 79 | assert_eq!( 80 | get_possible_return_values_of_func( 81 | funcname, 82 | &proj, 83 | Config::default(), 84 | Some(vec!(ParameterVal::ExactValue(3))), 85 | None, 86 | 5 87 | ), 88 | PossibleSolutions::exactly_one(ReturnValue::Return(3)), 89 | ) 90 | } 91 | 92 | // The following tests essentially assume that the simple cross-module call tests are passing 93 | 94 | #[test] 95 | fn cross_module_read_global() { 96 | let funcname = "cross_module_read_global"; 97 | init_logging(); 98 | let proj = get_cross_module_project(); 99 | assert_eq!( 100 | get_possible_return_values_of_func( 101 | funcname, 102 | &proj, 103 | Config::default(), 104 | Some(vec![]), 105 | None, 106 | 5 107 | ), 108 | PossibleSolutions::exactly_one(ReturnValue::Return(3)), 109 | ); 110 | } 111 | 112 | #[test] 113 | fn cross_module_read_global_via_call() { 114 | let funcname = "cross_module_read_global_via_call"; 115 | init_logging(); 116 | let proj = get_cross_module_project(); 117 | assert_eq!( 118 | get_possible_return_values_of_func( 119 | funcname, 120 | &proj, 121 | Config::default(), 122 | Some(vec![]), 123 | None, 124 | 5 125 | ), 126 | PossibleSolutions::exactly_one(ReturnValue::Return(3)), 127 | ); 128 | } 129 | 130 | #[test] 131 | fn cross_module_modify_global() { 132 | let funcname = "cross_module_modify_global"; 133 | init_logging(); 134 | let proj = get_cross_module_project(); 135 | assert_eq!( 136 | get_possible_return_values_of_func( 137 | funcname, 138 | &proj, 139 | Config::default(), 140 | Some(vec![ParameterVal::ExactValue(3)]), 141 | None, 142 | 5 143 | ), 144 | PossibleSolutions::exactly_one(ReturnValue::Return(3)), 145 | ); 146 | } 147 | 148 | #[test] 149 | fn cross_module_modify_global_via_call() { 150 | let funcname = "cross_module_modify_global_via_call"; 151 | init_logging(); 152 | let proj = get_cross_module_project(); 153 | assert_eq!( 154 | get_possible_return_values_of_func( 155 | funcname, 156 | &proj, 157 | Config::default(), 158 | Some(vec![ParameterVal::ExactValue(3)]), 159 | None, 160 | 5 161 | ), 162 | PossibleSolutions::exactly_one(ReturnValue::Return(3)), 163 | ); 164 | } 165 | 166 | #[test] 167 | fn globals_initialization() { 168 | let modnames = &[ 169 | "tests/bcfiles/globals_initialization_1.bc", 170 | "tests/bcfiles/globals_initialization_2.bc", 171 | ]; 172 | let funcname = "foo"; 173 | init_logging(); 174 | let proj = Project::from_bc_paths(modnames) 175 | .unwrap_or_else(|e| panic!("Failed to create project: {}", e)); 176 | assert_eq!( 177 | get_possible_return_values_of_func( 178 | funcname, 179 | &proj, 180 | Config::default(), 181 | Some(vec![]), 182 | None, 183 | 5 184 | ), 185 | PossibleSolutions::exactly_one(ReturnValue::Return(1052)), 186 | ) 187 | } 188 | -------------------------------------------------------------------------------- /tests/memory_tests.rs: -------------------------------------------------------------------------------- 1 | use haybale::backend::DefaultBackend; 2 | use haybale::config::NullPointerChecking; 3 | use haybale::*; 4 | 5 | fn init_logging() { 6 | // capture log messages with test harness 7 | let _ = env_logger::builder().is_test(true).try_init(); 8 | } 9 | 10 | fn get_project() -> Project { 11 | let modname = "tests/bcfiles/memory.bc"; 12 | Project::from_bc_path(modname) 13 | .unwrap_or_else(|e| panic!("Failed to parse module {:?}: {}", modname, e)) 14 | } 15 | 16 | #[test] 17 | fn load_and_store() { 18 | let funcname = "load_and_store"; 19 | init_logging(); 20 | let proj = get_project(); 21 | let args = find_zero_of_func( 22 | funcname, 23 | &proj, 24 | Config::default(), 25 | Some(vec![ 26 | ParameterVal::NonNullPointer, 27 | ParameterVal::Unconstrained, 28 | ]), 29 | ) 30 | .unwrap_or_else(|r| panic!("{}", r)) 31 | .expect("Failed to find zero of the function"); 32 | assert_eq!(args.len(), 2); 33 | assert_eq!(args[1], SolutionValue::I32(3)); 34 | } 35 | 36 | #[test] 37 | fn local_ptr() { 38 | let funcname = "local_ptr"; 39 | init_logging(); 40 | let proj = get_project(); 41 | let args = find_zero_of_func(funcname, &proj, Config::default(), None) 42 | .unwrap_or_else(|r| panic!("{}", r)) 43 | .expect("Failed to find zero of the function"); 44 | assert_eq!(args.len(), 1); 45 | assert_eq!(args[0], SolutionValue::I32(3)); 46 | } 47 | 48 | #[test] 49 | fn overwrite() { 50 | let funcname = "overwrite"; 51 | init_logging(); 52 | let proj = get_project(); 53 | let args = find_zero_of_func( 54 | funcname, 55 | &proj, 56 | Config::default(), 57 | Some(vec![ 58 | ParameterVal::NonNullPointer, 59 | ParameterVal::Unconstrained, 60 | ]), 61 | ) 62 | .unwrap_or_else(|r| panic!("{}", r)) 63 | .expect("Failed to find zero of the function"); 64 | assert_eq!(args.len(), 2); 65 | assert_eq!(args[1], SolutionValue::I32(3)); 66 | } 67 | 68 | #[test] 69 | fn load_and_store_mult() { 70 | let funcname = "load_and_store_mult"; 71 | init_logging(); 72 | let proj = get_project(); 73 | let args = find_zero_of_func( 74 | funcname, 75 | &proj, 76 | Config::default(), 77 | Some(vec![ 78 | ParameterVal::NonNullPointer, 79 | ParameterVal::Unconstrained, 80 | ]), 81 | ) 82 | .unwrap_or_else(|r| panic!("{}", r)) 83 | .expect("Failed to find zero of the function"); 84 | assert_eq!(args.len(), 2); 85 | assert_eq!(args[1], SolutionValue::I32(3)); 86 | } 87 | 88 | #[test] 89 | fn array() { 90 | let funcname = "array"; 91 | init_logging(); 92 | let proj = get_project(); 93 | let config_no_npc: Config = { 94 | let mut config = Config::default(); 95 | config.null_pointer_checking = NullPointerChecking::None; // otherwise this test fails, as ptr[10] could be NULL for the correct value of ptr 96 | config 97 | }; 98 | let args = find_zero_of_func(funcname, &proj, config_no_npc, None) 99 | .unwrap_or_else(|r| panic!("{}", r)) 100 | .expect("Failed to find zero of the function"); 101 | assert_eq!(args.len(), 2); 102 | assert_eq!(args[1], SolutionValue::I32(3)); 103 | // alternately, it should also work to allocate the parameter and still use null-pointer checking 104 | let args = find_zero_of_func( 105 | funcname, 106 | &proj, 107 | Config::default(), 108 | Some(vec![ 109 | ParameterVal::PointerToAllocated(54 * 4), 110 | ParameterVal::Unconstrained, 111 | ]), 112 | ) 113 | .unwrap_or_else(|r| panic!("{}", r)) 114 | .expect("Failed to find zero of the function"); 115 | assert_eq!(args.len(), 2); 116 | assert_eq!(args[1], SolutionValue::I32(3)); 117 | } 118 | 119 | #[test] 120 | fn pointer_arith() { 121 | let funcname = "pointer_arith"; 122 | init_logging(); 123 | let proj = get_project(); 124 | let config_no_npc: Config = { 125 | let mut config = Config::default(); 126 | config.null_pointer_checking = NullPointerChecking::None; // otherwise this test fails, as e.g. ptr[2] or ptr[5] or something could be NULL, for the correct value of ptr 127 | config 128 | }; 129 | let args = find_zero_of_func(funcname, &proj, config_no_npc, None) 130 | .unwrap_or_else(|r| panic!("{}", r)) 131 | .expect("Failed to find zero of the function"); 132 | assert_eq!(args.len(), 2); 133 | assert_eq!(args[1], SolutionValue::I32(3)); 134 | // alternately, it should also work to allocate the parameter and still use null-pointer checking 135 | let args = find_zero_of_func( 136 | funcname, 137 | &proj, 138 | Config::default(), 139 | Some(vec![ 140 | ParameterVal::PointerToAllocated(54 * 4), 141 | ParameterVal::Unconstrained, 142 | ]), 143 | ) 144 | .unwrap_or_else(|r| panic!("{}", r)) 145 | .expect("Failed to find zero of the function"); 146 | assert_eq!(args.len(), 2); 147 | assert_eq!(args[1], SolutionValue::I32(3)); 148 | } 149 | 150 | #[test] 151 | fn pointer_compare() { 152 | let funcname = "pointer_compare"; 153 | init_logging(); 154 | let proj = get_project(); 155 | let args = find_zero_of_func(funcname, &proj, Config::default(), None) 156 | .unwrap_or_else(|r| panic!("{}", r)) 157 | .expect("Failed to find zero of the function"); 158 | assert_eq!(args.len(), 1); 159 | assert_eq!(args[0], SolutionValue::I32(3)); 160 | } 161 | -------------------------------------------------------------------------------- /tests/bcfiles/simd_cl.ll: -------------------------------------------------------------------------------- 1 | ; ModuleID = 'simd_cl.cl' 2 | source_filename = "simd_cl.cl" 3 | target datalayout = "e-m:o-i64:64-f80:128-n8:16:32:64-S128" 4 | target triple = "x86_64-apple-macosx10.15.0" 5 | 6 | ; Function Attrs: norecurse nounwind readnone ssp uwtable 7 | define i32 @simd_add(i32, i32) local_unnamed_addr #0 { 8 | %3 = insertelement <4 x i32> undef, i32 %0, i32 0 9 | %4 = shufflevector <4 x i32> %3, <4 x i32> undef, <4 x i32> zeroinitializer 10 | %5 = insertelement <4 x i32> undef, i32 %1, i32 0 11 | %6 = add i32 %1, 1 12 | %7 = insertelement <4 x i32> %5, i32 %6, i32 1 13 | %8 = add i32 %1, 2 14 | %9 = insertelement <4 x i32> %7, i32 %8, i32 2 15 | %10 = add i32 %1, 3 16 | %11 = insertelement <4 x i32> %9, i32 %10, i32 3 17 | %12 = add <4 x i32> %11, %4 18 | %13 = shufflevector <4 x i32> %12, <4 x i32> undef, <4 x i32> 19 | %14 = add <4 x i32> %12, %13 20 | %15 = shufflevector <4 x i32> %14, <4 x i32> undef, <4 x i32> 21 | %16 = add <4 x i32> %14, %15 22 | %17 = extractelement <4 x i32> %16, i32 0 23 | ret i32 %17 24 | } 25 | 26 | ; Function Attrs: norecurse nounwind readnone ssp uwtable 27 | define i32 @simd_ops(i32, i32) local_unnamed_addr #0 { 28 | %3 = insertelement <4 x i32> undef, i32 %0, i32 0 29 | %4 = shufflevector <4 x i32> %3, <4 x i32> undef, <4 x i32> zeroinitializer 30 | %5 = insertelement <4 x i32> undef, i32 %1, i32 0 31 | %6 = add i32 %1, 1 32 | %7 = insertelement <4 x i32> %5, i32 %6, i32 1 33 | %8 = add i32 %1, 2 34 | %9 = insertelement <4 x i32> %7, i32 %8, i32 2 35 | %10 = add i32 %1, 3 36 | %11 = insertelement <4 x i32> %9, i32 %10, i32 3 37 | %12 = add <4 x i32> %11, %4 38 | %13 = mul <4 x i32> %12, 39 | %14 = add <4 x i32> %13, 40 | %15 = xor <4 x i32> %4, 41 | %16 = and <4 x i32> %14, %15 42 | %17 = or <4 x i32> %16, %11 43 | %18 = lshr <4 x i32> %17, 44 | %19 = shl <4 x i32> %18, 45 | %20 = shufflevector <4 x i32> %19, <4 x i32> undef, <4 x i32> 46 | %21 = add <4 x i32> %19, %20 47 | %22 = shufflevector <4 x i32> %21, <4 x i32> undef, <4 x i32> 48 | %23 = add <4 x i32> %21, %22 49 | %24 = extractelement <4 x i32> %23, i32 0 50 | ret i32 %24 51 | } 52 | 53 | ; Function Attrs: norecurse nounwind readnone ssp uwtable 54 | define i32 @simd_select(i32, i32) local_unnamed_addr #0 { 55 | %3 = insertelement <4 x i32> undef, i32 %0, i32 0 56 | %4 = shufflevector <4 x i32> %3, <4 x i32> undef, <4 x i32> zeroinitializer 57 | %5 = insertelement <4 x i32> undef, i32 %1, i32 0 58 | %6 = add i32 %1, 1 59 | %7 = insertelement <4 x i32> %5, i32 %6, i32 1 60 | %8 = add i32 %1, 2 61 | %9 = insertelement <4 x i32> %7, i32 %8, i32 2 62 | %10 = add i32 %1, 3 63 | %11 = insertelement <4 x i32> %9, i32 %10, i32 3 64 | %12 = icmp ult <4 x i32> %4, %11 65 | %13 = select <4 x i1> %12, <4 x i32> %4, <4 x i32> %11 66 | %14 = shufflevector <4 x i32> %13, <4 x i32> undef, <4 x i32> 67 | %15 = add <4 x i32> %13, %14 68 | %16 = shufflevector <4 x i32> %15, <4 x i32> undef, <4 x i32> 69 | %17 = add <4 x i32> %15, %16 70 | %18 = extractelement <4 x i32> %17, i32 0 71 | ret i32 %18 72 | } 73 | 74 | ; Function Attrs: norecurse nounwind readnone ssp uwtable 75 | define i32 @simd_typeconversions(i32, i32) local_unnamed_addr #0 { 76 | %3 = insertelement <2 x i32> undef, i32 %1, i32 0 77 | %4 = shufflevector <2 x i32> %3, <2 x i32> undef, <2 x i32> zeroinitializer 78 | %5 = add <2 x i32> %4, 79 | %6 = add i32 %1, 30 80 | %7 = zext i32 %0 to i64 81 | %8 = insertelement <4 x i64> undef, i64 %7, i32 0 82 | %9 = shufflevector <4 x i64> %8, <4 x i64> undef, <4 x i32> zeroinitializer 83 | %10 = zext i32 %1 to i64 84 | %11 = insertelement <4 x i64> undef, i64 %10, i32 0 85 | %12 = zext <2 x i32> %5 to <2 x i64> 86 | %13 = extractelement <2 x i64> %12, i32 0 87 | %14 = insertelement <4 x i64> %11, i64 %13, i32 1 88 | %15 = extractelement <2 x i64> %12, i32 1 89 | %16 = insertelement <4 x i64> %14, i64 %15, i32 2 90 | %17 = zext i32 %6 to i64 91 | %18 = insertelement <4 x i64> %16, i64 %17, i32 3 92 | %19 = sub <4 x i64> %18, %9 93 | %20 = trunc <4 x i64> %19 to <4 x i32> 94 | %21 = shufflevector <4 x i32> %20, <4 x i32> undef, <4 x i32> 95 | %22 = add <4 x i32> %21, %20 96 | %23 = shufflevector <4 x i32> %22, <4 x i32> undef, <4 x i32> 97 | %24 = add <4 x i32> %22, %23 98 | %25 = extractelement <4 x i32> %24, i32 0 99 | ret i32 %25 100 | } 101 | 102 | attributes #0 = { norecurse nounwind readnone ssp uwtable "correctly-rounded-divide-sqrt-fp-math"="false" "denorms-are-zero"="false" "disable-tail-calls"="false" "less-precise-fpmad"="false" "min-legal-vector-width"="0" "no-frame-pointer-elim"="true" "no-frame-pointer-elim-non-leaf" "no-infs-fp-math"="false" "no-jump-tables"="false" "no-nans-fp-math"="false" "no-signed-zeros-fp-math"="false" "no-trapping-math"="false" "stack-protector-buffer-size"="8" "target-cpu"="penryn" "target-features"="+cx16,+cx8,+fxsr,+mmx,+sahf,+sse,+sse2,+sse3,+sse4.1,+ssse3,+x87" "unsafe-fp-math"="false" "use-soft-float"="false" } 103 | 104 | !llvm.module.flags = !{!0, !1} 105 | !opencl.ocl.version = !{!2} 106 | !llvm.ident = !{!3} 107 | 108 | !0 = !{i32 1, !"wchar_size", i32 4} 109 | !1 = !{i32 7, !"PIC Level", i32 2} 110 | !2 = !{i32 1, i32 0} 111 | !3 = !{!"clang version 9.0.1 "} 112 | -------------------------------------------------------------------------------- /tests/bcfiles/functionptr.ll: -------------------------------------------------------------------------------- 1 | ; ModuleID = 'functionptr.c' 2 | source_filename = "functionptr.c" 3 | target datalayout = "e-m:o-i64:64-f80:128-n8:16:32:64-S128" 4 | target triple = "x86_64-apple-macosx10.15.0" 5 | 6 | %struct.StructWithFuncPtr = type { i32, i32 (i32, i32)* } 7 | 8 | ; Function Attrs: noinline norecurse nounwind readnone ssp uwtable 9 | define i32 @foo(i32, i32) #0 { 10 | %3 = add nsw i32 %1, 3 11 | %4 = mul nsw i32 %3, %0 12 | ret i32 %4 13 | } 14 | 15 | ; Function Attrs: noinline norecurse nounwind readnone ssp uwtable 16 | define i32 @bar(i32, i32) #0 { 17 | %3 = sub nsw i32 %0, %1 18 | ret i32 %3 19 | } 20 | 21 | ; Function Attrs: noinline nounwind ssp uwtable 22 | define i32 @calls_fptr(i32 (i32, i32)*, i32) local_unnamed_addr #1 { 23 | %3 = alloca i32 (i32, i32)*, align 8 24 | store volatile i32 (i32, i32)* %0, i32 (i32, i32)** %3, align 8, !tbaa !3 25 | %4 = load volatile i32 (i32, i32)*, i32 (i32, i32)** %3, align 8, !tbaa !3 26 | %5 = tail call i32 %4(i32 2, i32 3) #4 27 | %6 = add nsw i32 %5, %1 28 | ret i32 %6 29 | } 30 | 31 | ; Function Attrs: noinline norecurse nounwind readnone ssp uwtable 32 | define nonnull i32 (i32, i32)* @get_function_ptr(i1 zeroext) local_unnamed_addr #0 { 33 | %2 = select i1 %0, i32 (i32, i32)* @foo, i32 (i32, i32)* @bar 34 | ret i32 (i32, i32)* %2 35 | } 36 | 37 | ; Function Attrs: nounwind ssp uwtable 38 | define i32 @fptr_driver() local_unnamed_addr #2 { 39 | %1 = alloca i32 (i32, i32)*, align 8 40 | %2 = bitcast i32 (i32, i32)** %1 to i8* 41 | call void @llvm.lifetime.start.p0i8(i64 8, i8* nonnull %2) 42 | %3 = tail call i32 (i32, i32)* @get_function_ptr(i1 zeroext true) 43 | store volatile i32 (i32, i32)* %3, i32 (i32, i32)** %1, align 8, !tbaa !3 44 | %4 = load volatile i32 (i32, i32)*, i32 (i32, i32)** %1, align 8, !tbaa !3 45 | %5 = tail call i32 @calls_fptr(i32 (i32, i32)* %4, i32 10) 46 | call void @llvm.lifetime.end.p0i8(i64 8, i8* nonnull %2) 47 | ret i32 %5 48 | } 49 | 50 | ; Function Attrs: argmemonly nounwind 51 | declare void @llvm.lifetime.start.p0i8(i64 immarg, i8* nocapture) #3 52 | 53 | ; Function Attrs: argmemonly nounwind 54 | declare void @llvm.lifetime.end.p0i8(i64 immarg, i8* nocapture) #3 55 | 56 | ; Function Attrs: noinline nounwind ssp uwtable 57 | define i32 @calls_through_struct(%struct.StructWithFuncPtr*) local_unnamed_addr #1 { 58 | %2 = getelementptr inbounds %struct.StructWithFuncPtr, %struct.StructWithFuncPtr* %0, i64 0, i32 1 59 | %3 = load volatile i32 (i32, i32)*, i32 (i32, i32)** %2, align 8, !tbaa !7 60 | %4 = getelementptr inbounds %struct.StructWithFuncPtr, %struct.StructWithFuncPtr* %0, i64 0, i32 0 61 | %5 = load volatile i32, i32* %4, align 8, !tbaa !10 62 | %6 = tail call i32 %3(i32 %5, i32 2) #4 63 | ret i32 %6 64 | } 65 | 66 | ; Function Attrs: nounwind ssp uwtable 67 | define i32 @struct_driver() local_unnamed_addr #2 { 68 | %1 = alloca %struct.StructWithFuncPtr, align 8 69 | %2 = bitcast %struct.StructWithFuncPtr* %1 to i8* 70 | call void @llvm.lifetime.start.p0i8(i64 16, i8* nonnull %2) #4 71 | call void @llvm.memset.p0i8.i64(i8* nonnull align 8 %2, i8 0, i64 16, i1 true) 72 | %3 = tail call i32 (i32, i32)* @get_function_ptr(i1 zeroext true) 73 | %4 = getelementptr inbounds %struct.StructWithFuncPtr, %struct.StructWithFuncPtr* %1, i64 0, i32 1 74 | store volatile i32 (i32, i32)* %3, i32 (i32, i32)** %4, align 8, !tbaa !7 75 | %5 = getelementptr inbounds %struct.StructWithFuncPtr, %struct.StructWithFuncPtr* %1, i64 0, i32 0 76 | store volatile i32 3, i32* %5, align 8, !tbaa !10 77 | %6 = call i32 @calls_through_struct(%struct.StructWithFuncPtr* nonnull %1) 78 | call void @llvm.lifetime.end.p0i8(i64 16, i8* nonnull %2) #4 79 | ret i32 %6 80 | } 81 | 82 | ; Function Attrs: argmemonly nounwind 83 | declare void @llvm.memset.p0i8.i64(i8* nocapture writeonly, i8, i64, i1 immarg) #3 84 | 85 | attributes #0 = { noinline norecurse nounwind readnone ssp uwtable "correctly-rounded-divide-sqrt-fp-math"="false" "disable-tail-calls"="false" "less-precise-fpmad"="false" "min-legal-vector-width"="0" "no-frame-pointer-elim"="true" "no-frame-pointer-elim-non-leaf" "no-infs-fp-math"="false" "no-jump-tables"="false" "no-nans-fp-math"="false" "no-signed-zeros-fp-math"="false" "no-trapping-math"="false" "stack-protector-buffer-size"="8" "target-cpu"="penryn" "target-features"="+cx16,+cx8,+fxsr,+mmx,+sahf,+sse,+sse2,+sse3,+sse4.1,+ssse3,+x87" "unsafe-fp-math"="false" "use-soft-float"="false" } 86 | attributes #1 = { noinline nounwind ssp uwtable "correctly-rounded-divide-sqrt-fp-math"="false" "disable-tail-calls"="false" "less-precise-fpmad"="false" "min-legal-vector-width"="0" "no-frame-pointer-elim"="true" "no-frame-pointer-elim-non-leaf" "no-infs-fp-math"="false" "no-jump-tables"="false" "no-nans-fp-math"="false" "no-signed-zeros-fp-math"="false" "no-trapping-math"="false" "stack-protector-buffer-size"="8" "target-cpu"="penryn" "target-features"="+cx16,+cx8,+fxsr,+mmx,+sahf,+sse,+sse2,+sse3,+sse4.1,+ssse3,+x87" "unsafe-fp-math"="false" "use-soft-float"="false" } 87 | attributes #2 = { nounwind ssp uwtable "correctly-rounded-divide-sqrt-fp-math"="false" "disable-tail-calls"="false" "less-precise-fpmad"="false" "min-legal-vector-width"="0" "no-frame-pointer-elim"="true" "no-frame-pointer-elim-non-leaf" "no-infs-fp-math"="false" "no-jump-tables"="false" "no-nans-fp-math"="false" "no-signed-zeros-fp-math"="false" "no-trapping-math"="false" "stack-protector-buffer-size"="8" "target-cpu"="penryn" "target-features"="+cx16,+cx8,+fxsr,+mmx,+sahf,+sse,+sse2,+sse3,+sse4.1,+ssse3,+x87" "unsafe-fp-math"="false" "use-soft-float"="false" } 88 | attributes #3 = { argmemonly nounwind } 89 | attributes #4 = { nounwind } 90 | 91 | !llvm.module.flags = !{!0, !1} 92 | !llvm.ident = !{!2} 93 | 94 | !0 = !{i32 1, !"wchar_size", i32 4} 95 | !1 = !{i32 7, !"PIC Level", i32 2} 96 | !2 = !{!"clang version 9.0.1 "} 97 | !3 = !{!4, !4, i64 0} 98 | !4 = !{!"any pointer", !5, i64 0} 99 | !5 = !{!"omnipotent char", !6, i64 0} 100 | !6 = !{!"Simple C/C++ TBAA"} 101 | !7 = !{!8, !4, i64 8} 102 | !8 = !{!"StructWithFuncPtr", !9, i64 0, !4, i64 8} 103 | !9 = !{!"int", !5, i64 0} 104 | !10 = !{!8, !9, i64 0} 105 | -------------------------------------------------------------------------------- /src/demangling.rs: -------------------------------------------------------------------------------- 1 | use crate::project::Project; 2 | 3 | /// Enum used for the `demangling` option in `Config`. 4 | #[derive(PartialEq, Eq, Clone, Copy, Debug)] 5 | pub enum Demangling { 6 | /// Don't try to demangle 7 | NoDemangling, 8 | /// Try to demangle using the C++ demangler (suitable for `Project`s containing C++ code). 9 | /// Names that fail to demangle will simply be printed as-is. 10 | CPP, 11 | /// Try to demangle using the Rust demangler (suitable for `Project`s containing Rust code). 12 | /// Names that fail to demangle will simply be printed as-is. 13 | Rust, 14 | } 15 | 16 | impl Demangling { 17 | /// Attempts to demangle the given function name, as appropriate based on the 18 | /// `Demangling` setting. 19 | // 20 | // (takes `self` by value because `self` is `Copy`) 21 | pub fn maybe_demangle(self, funcname: &str) -> String { 22 | match self { 23 | Demangling::NoDemangling => funcname.to_owned(), 24 | Demangling::CPP => cpp_demangle_or_id(funcname), 25 | Demangling::Rust => rust_demangle_or_id(funcname), 26 | } 27 | } 28 | 29 | /// Guesses an appropriate `Demangling` for the given `Project`. 30 | pub fn autodetect(proj: &Project) -> Self { 31 | // our autodetection is pretty unsophisticated right now, 32 | // but something is better than nothing 33 | 34 | // if any file in the `Project` comes from a source file 35 | // ending in `.rs`, then use Rust demangling. 36 | // Empirically, bitcode generated by `rustc` may have a "source 37 | // filename" ending in `cgu.0` instead (for example, our test file 38 | // `panic.rs` is compiled to a bitcode file with a "source filename" 39 | // of `panic.3a1fbbbh-cgu.0`), so also check for filenames ending in 40 | // `u.0`. (False positives aren't the end of the world, because any 41 | // symbols that aren't actually valid Rust symbols will be passed 42 | // through the demangler unchanged.) 43 | // TODO figure out a tighter test here, hopefully we can avoid both 44 | // false positives and false negatives. 45 | if proj 46 | .module_source_file_names() 47 | .any(|name| name.ends_with(".rs") || name.ends_with("u.0")) 48 | { 49 | return Demangling::Rust; 50 | } 51 | 52 | // otherwise, if any file in the `Project` comes from a source 53 | // file ending in `.cpp`, then use C++ demangling 54 | if proj 55 | .module_source_file_names() 56 | .any(|name| name.ends_with(".cpp")) 57 | { 58 | return Demangling::CPP; 59 | } 60 | 61 | // otherwise give up and don't try to demangle 62 | Demangling::NoDemangling 63 | } 64 | } 65 | 66 | /// Helper function to demangle function names with the C++ demangler. 67 | /// 68 | /// Returns `Some` if successfully demangled, or `None` if any error occurs 69 | /// (for instance, if `funcname` isn't a valid C++ mangled name) 70 | pub(crate) fn try_cpp_demangle(funcname: &str) -> Option { 71 | let opts = cpp_demangle::DemangleOptions { no_params: true }; 72 | cpp_demangle::Symbol::new(funcname) 73 | .ok() 74 | .and_then(|sym| sym.demangle(&opts).ok()) 75 | } 76 | 77 | /// Like `try_cpp_demangle()`, but just returns the input string unmodified in 78 | /// the case of any error, rather than returning `None`. 79 | pub(crate) fn cpp_demangle_or_id(funcname: &str) -> String { 80 | try_cpp_demangle(funcname).unwrap_or_else(|| funcname.to_owned()) 81 | } 82 | 83 | /// Helper function to demangle function names with the Rust demangler. 84 | /// 85 | /// Returns `Some` if successfully demangled, or `None` if any error occurs 86 | /// (for instance, if `funcname` isn't a valid Rust mangled name) 87 | pub(crate) fn try_rust_demangle(funcname: &str) -> Option { 88 | rustc_demangle::try_demangle(funcname) 89 | .ok() 90 | .map(|demangled| format!("{:#}", demangled)) 91 | } 92 | 93 | /// Like `try_rust_demangle()`, but just returns the input string unmodified in 94 | /// the case of any error, rather than returning `None`. 95 | pub(crate) fn rust_demangle_or_id(funcname: &str) -> String { 96 | format!("{:#}", rustc_demangle::demangle(funcname)) 97 | } 98 | 99 | #[cfg(test)] 100 | mod tests { 101 | use super::*; 102 | 103 | #[test] 104 | fn autodetect() -> Result<(), String> { 105 | // A `Project` from a single C file 106 | let c_proj = Project::from_bc_path("tests/bcfiles/basic.bc")?; 107 | assert_eq!(Demangling::autodetect(&c_proj), Demangling::NoDemangling); 108 | 109 | // A `Project` from a single C++ file 110 | let cpp_proj = Project::from_bc_path("tests/bcfiles/throwcatch.bc")?; 111 | assert_eq!(Demangling::autodetect(&cpp_proj), Demangling::CPP); 112 | 113 | // A `Project` from a single Rust file 114 | let rust_proj = Project::from_bc_path("tests/bcfiles/panic.bc")?; 115 | assert_eq!(Demangling::autodetect(&rust_proj), Demangling::Rust); 116 | 117 | // A `Project` containing multiple C files 118 | let c_proj = Project::from_bc_paths(&[ 119 | "tests/bcfiles/basic.bc", 120 | "tests/bcfiles/call.bc", 121 | "tests/bcfiles/globals.bc", 122 | "tests/bcfiles/simd.bc", 123 | ])?; 124 | assert_eq!(Demangling::autodetect(&c_proj), Demangling::NoDemangling); 125 | 126 | // A `Project` containing both C and Rust files 127 | let c_rust_proj = Project::from_bc_paths(&[ 128 | "tests/bcfiles/basic.bc", 129 | "tests/bcfiles/call.bc", 130 | "tests/bcfiles/panic.bc", 131 | ])?; 132 | assert_eq!(Demangling::autodetect(&c_rust_proj), Demangling::Rust); 133 | 134 | // A `Project` containing both C and C++ files 135 | let c_cpp_proj = Project::from_bc_paths(&[ 136 | "tests/bcfiles/basic.bc", 137 | "tests/bcfiles/call.bc", 138 | "tests/bcfiles/throwcatch.bc", 139 | ])?; 140 | assert_eq!(Demangling::autodetect(&c_cpp_proj), Demangling::CPP); 141 | 142 | Ok(()) 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /tests/simd_tests.rs: -------------------------------------------------------------------------------- 1 | use haybale::solver_utils::PossibleSolutions; 2 | use haybale::*; 3 | 4 | fn init_logging() { 5 | // capture log messages with test harness 6 | let _ = env_logger::builder().is_test(true).try_init(); 7 | } 8 | 9 | #[test] 10 | fn simd_add() { 11 | let funcname = "simd_add"; 12 | init_logging(); 13 | let proj = Project::from_bc_path("tests/bcfiles/simd_cl.bc") 14 | .unwrap_or_else(|e| panic!("Failed to parse simd_cl.bc module: {}", e)); 15 | 16 | // This function effectively computes 4x + 4y + 6. 17 | // So with x=3 and y=5, we should have 12 + 20 + 6 = 38. 18 | assert_eq!( 19 | get_possible_return_values_of_func( 20 | funcname, 21 | &proj, 22 | Config::default(), 23 | Some(vec![ 24 | ParameterVal::ExactValue(3), 25 | ParameterVal::ExactValue(5) 26 | ]), 27 | None, 28 | 5, 29 | ), 30 | PossibleSolutions::exactly_one(ReturnValue::Return(38)), 31 | ); 32 | } 33 | 34 | #[test] 35 | fn simd_ops() { 36 | let funcname = "simd_ops"; 37 | init_logging(); 38 | let proj = Project::from_bc_path("tests/bcfiles/simd_cl.bc") 39 | .unwrap_or_else(|e| panic!("Failed to parse simd_cl.bc module: {}", e)); 40 | 41 | // We compute the function's output for x=4, y=7 42 | let a_1: u32 = 4; 43 | let a_2: u32 = 4; 44 | let a_3: u32 = 4; 45 | let a_4: u32 = 4; 46 | let b_1: u32 = 7; 47 | let b_2: u32 = 8; 48 | let b_3: u32 = 9; 49 | let b_4: u32 = 10; 50 | let c_1: u32 = a_1 + b_1 - 3; 51 | let c_2: u32 = a_2 + b_2 - 3; 52 | let c_3: u32 = a_3 + b_3 - 3; 53 | let c_4: u32 = a_4 + b_4 - 3; 54 | let d_1: u32 = c_1 * 17; 55 | let d_2: u32 = c_2 * 17; 56 | let d_3: u32 = c_3 * 17; 57 | let d_4: u32 = c_4 * 17; 58 | let e_1: u32 = d_1 & (!a_1) | b_1; 59 | let e_2: u32 = d_2 & (!a_2) | b_2; 60 | let e_3: u32 = d_3 & (!a_3) | b_3; 61 | let e_4: u32 = d_4 & (!a_4) | b_4; 62 | let f_1: u32 = e_1 >> 2; 63 | let f_2: u32 = e_2 >> 2; 64 | let f_3: u32 = e_3 >> 2; 65 | let f_4: u32 = e_4 >> 2; 66 | let g_1: u32 = f_1 << 2; 67 | let g_2: u32 = f_2 << 3; 68 | let g_3: u32 = f_3 << 4; 69 | let g_4: u32 = f_4 << 5; 70 | let retval: u32 = g_1 + g_2 + g_3 + g_4; 71 | assert_eq!( 72 | get_possible_return_values_of_func( 73 | funcname, 74 | &proj, 75 | Config::default(), 76 | Some(vec![ 77 | ParameterVal::ExactValue(4), 78 | ParameterVal::ExactValue(7) 79 | ]), 80 | None, 81 | 5, 82 | ), 83 | PossibleSolutions::exactly_one(ReturnValue::Return(retval as u64)), 84 | ); 85 | } 86 | 87 | #[test] 88 | fn simd_select() { 89 | let funcname = "simd_select"; 90 | init_logging(); 91 | let proj = Project::from_bc_path("tests/bcfiles/simd_cl.bc") 92 | .unwrap_or_else(|e| panic!("Failed to parse simd_cl.bc module: {}", e)); 93 | 94 | // We compute the function's output for x=4, y=3 95 | let a_1: u32 = 4; 96 | let a_2: u32 = 4; 97 | let a_3: u32 = 4; 98 | let a_4: u32 = 4; 99 | let b_1: u32 = 3; 100 | let b_2: u32 = 4; 101 | let b_3: u32 = 5; 102 | let b_4: u32 = 6; 103 | let c_1: u32 = if a_1 < b_1 { a_1 } else { b_1 }; 104 | let c_2: u32 = if a_2 < b_2 { a_2 } else { b_2 }; 105 | let c_3: u32 = if a_3 < b_3 { a_3 } else { b_3 }; 106 | let c_4: u32 = if a_4 < b_4 { a_4 } else { b_4 }; 107 | let retval = c_1 + c_2 + c_3 + c_4; 108 | assert_eq!( 109 | get_possible_return_values_of_func( 110 | funcname, 111 | &proj, 112 | Config::default(), 113 | Some(vec![ 114 | ParameterVal::ExactValue(4), 115 | ParameterVal::ExactValue(3) 116 | ]), 117 | None, 118 | 5, 119 | ), 120 | PossibleSolutions::exactly_one(ReturnValue::Return(retval as u64)), 121 | ); 122 | } 123 | 124 | #[test] 125 | fn simd_add_autovectorized() { 126 | let funcname = "simd_add_autovectorized"; 127 | init_logging(); 128 | let proj = Project::from_bc_path("tests/bcfiles/simd.bc") 129 | .unwrap_or_else(|e| panic!("Failed to parse simd.bc module: {}", e)); 130 | 131 | let x_sum: u32 = (0 .. 16).sum(); 132 | let y_sum: u32 = (2 .. 18).sum(); 133 | let z_sum: u32 = x_sum + y_sum; 134 | assert_eq!( 135 | get_possible_return_values_of_func( 136 | funcname, 137 | &proj, 138 | Config::default(), 139 | Some(vec![]), 140 | None, 141 | 5, 142 | ), 143 | PossibleSolutions::exactly_one(ReturnValue::Return(z_sum as u64)), 144 | ); 145 | } 146 | 147 | #[test] 148 | fn simd_typeconversions() { 149 | let funcname = "simd_typeconversions"; 150 | init_logging(); 151 | let proj = Project::from_bc_path("tests/bcfiles/simd_cl.bc") 152 | .unwrap_or_else(|e| panic!("Failed to parse simd_cl.bc module: {}", e)); 153 | 154 | // We compute the function's output for x=3, y=5 155 | let a_1: u32 = 3; 156 | let a_2: u32 = 3; 157 | let a_3: u32 = 3; 158 | let a_4: u32 = 3; 159 | let b_1: u32 = 5; 160 | let b_2: u32 = 15; 161 | let b_3: u32 = 8; 162 | let b_4: u32 = 35; 163 | let c_1: u64 = u64::from(a_1); 164 | let c_2: u64 = u64::from(a_2); 165 | let c_3: u64 = u64::from(a_3); 166 | let c_4: u64 = u64::from(a_4); 167 | let d_1: u64 = u64::from(b_1); 168 | let d_2: u64 = u64::from(b_2); 169 | let d_3: u64 = u64::from(b_3); 170 | let d_4: u64 = u64::from(b_4); 171 | let e_1: u64 = d_1 - c_1; 172 | let e_2: u64 = d_2 - c_2; 173 | let e_3: u64 = d_3 - c_3; 174 | let e_4: u64 = d_4 - c_4; 175 | let f_1: u32 = e_1 as u32; 176 | let f_2: u32 = e_2 as u32; 177 | let f_3: u32 = e_3 as u32; 178 | let f_4: u32 = e_4 as u32; 179 | let retval = f_1 + f_2 + f_3 + f_4; 180 | assert_eq!( 181 | get_possible_return_values_of_func( 182 | funcname, 183 | &proj, 184 | Config::default(), 185 | Some(vec![ 186 | ParameterVal::ExactValue(3), 187 | ParameterVal::ExactValue(5) 188 | ]), 189 | None, 190 | 5, 191 | ), 192 | PossibleSolutions::exactly_one(ReturnValue::Return(retval as u64)), 193 | ) 194 | } 195 | -------------------------------------------------------------------------------- /tests/bcfiles/memory.ll: -------------------------------------------------------------------------------- 1 | ; ModuleID = 'memory.c' 2 | source_filename = "memory.c" 3 | target datalayout = "e-m:o-i64:64-f80:128-n8:16:32:64-S128" 4 | target triple = "x86_64-apple-macosx10.15.0" 5 | 6 | ; Function Attrs: nofree norecurse nounwind ssp uwtable 7 | define i32 @load_and_store(i32*, i32) local_unnamed_addr #0 { 8 | %3 = add nsw i32 %1, -3 9 | store volatile i32 %3, i32* %0, align 4, !tbaa !3 10 | %4 = load volatile i32, i32* %0, align 4, !tbaa !3 11 | ret i32 %4 12 | } 13 | 14 | ; Function Attrs: nounwind ssp uwtable 15 | define i32 @local_ptr(i32) local_unnamed_addr #1 { 16 | %2 = alloca i32, align 4 17 | %3 = bitcast i32* %2 to i8* 18 | call void @llvm.lifetime.start.p0i8(i64 4, i8* nonnull %3) 19 | %4 = add nsw i32 %0, -3 20 | store volatile i32 %4, i32* %2, align 4, !tbaa !3 21 | %5 = load volatile i32, i32* %2, align 4, !tbaa !3 22 | call void @llvm.lifetime.end.p0i8(i64 4, i8* nonnull %3) 23 | ret i32 %5 24 | } 25 | 26 | ; Function Attrs: argmemonly nounwind 27 | declare void @llvm.lifetime.start.p0i8(i64 immarg, i8* nocapture) #2 28 | 29 | ; Function Attrs: argmemonly nounwind 30 | declare void @llvm.lifetime.end.p0i8(i64 immarg, i8* nocapture) #2 31 | 32 | ; Function Attrs: nofree norecurse nounwind ssp uwtable 33 | define i32 @overwrite(i32*, i32) local_unnamed_addr #0 { 34 | store volatile i32 0, i32* %0, align 4, !tbaa !3 35 | store volatile i32 2, i32* %0, align 4, !tbaa !3 36 | %3 = add nsw i32 %1, -3 37 | store volatile i32 %3, i32* %0, align 4, !tbaa !3 38 | %4 = load volatile i32, i32* %0, align 4, !tbaa !3 39 | ret i32 %4 40 | } 41 | 42 | ; Function Attrs: nofree norecurse nounwind ssp uwtable 43 | define i32 @load_and_store_mult(i32*, i32) local_unnamed_addr #0 { 44 | store volatile i32 %1, i32* %0, align 4, !tbaa !3 45 | %3 = load volatile i32, i32* %0, align 4, !tbaa !3 46 | %4 = add nsw i32 %3, 10 47 | store volatile i32 %4, i32* %0, align 4, !tbaa !3 48 | %5 = load volatile i32, i32* %0, align 4, !tbaa !3 49 | %6 = add nsw i32 %5, -13 50 | store volatile i32 %6, i32* %0, align 4, !tbaa !3 51 | %7 = load volatile i32, i32* %0, align 4, !tbaa !3 52 | ret i32 %7 53 | } 54 | 55 | ; Function Attrs: nofree norecurse nounwind ssp uwtable 56 | define i32 @array(i32*, i32) local_unnamed_addr #0 { 57 | %3 = add nsw i32 %1, -3 58 | %4 = getelementptr inbounds i32, i32* %0, i64 10 59 | store volatile i32 %3, i32* %4, align 4, !tbaa !3 60 | %5 = add nsw i32 %1, 3 61 | store volatile i32 %5, i32* %0, align 4, !tbaa !3 62 | %6 = load volatile i32, i32* %4, align 4, !tbaa !3 63 | ret i32 %6 64 | } 65 | 66 | ; Function Attrs: nofree norecurse nounwind ssp uwtable 67 | define i32 @pointer_arith(i32*, i32) local_unnamed_addr #0 { 68 | store volatile i32 %1, i32* %0, align 4, !tbaa !3 69 | %3 = getelementptr inbounds i32, i32* %0, i64 1 70 | %4 = add nsw i32 %1, -1 71 | store volatile i32 %4, i32* %3, align 4, !tbaa !3 72 | %5 = getelementptr inbounds i32, i32* %0, i64 2 73 | %6 = add nsw i32 %1, -2 74 | store volatile i32 %6, i32* %5, align 4, !tbaa !3 75 | %7 = getelementptr inbounds i32, i32* %0, i64 3 76 | %8 = add nsw i32 %1, -3 77 | store volatile i32 %8, i32* %7, align 4, !tbaa !3 78 | %9 = getelementptr inbounds i32, i32* %0, i64 4 79 | %10 = add nsw i32 %1, -4 80 | store volatile i32 %10, i32* %9, align 4, !tbaa !3 81 | %11 = getelementptr inbounds i32, i32* %0, i64 5 82 | %12 = add nsw i32 %1, -5 83 | store volatile i32 %12, i32* %11, align 4, !tbaa !3 84 | %13 = getelementptr inbounds i32, i32* %0, i64 6 85 | %14 = add nsw i32 %1, -6 86 | store volatile i32 %14, i32* %13, align 4, !tbaa !3 87 | %15 = load volatile i32, i32* %7, align 4, !tbaa !3 88 | ret i32 %15 89 | } 90 | 91 | ; Function Attrs: nounwind ssp uwtable 92 | define i32 @pointer_compare(i32) local_unnamed_addr #1 { 93 | %2 = alloca i32, align 4 94 | %3 = alloca i32, align 4 95 | %4 = alloca i32*, align 8 96 | %5 = alloca i32*, align 8 97 | %6 = bitcast i32* %2 to i8* 98 | call void @llvm.lifetime.start.p0i8(i64 4, i8* nonnull %6) #3 99 | %7 = bitcast i32* %3 to i8* 100 | call void @llvm.lifetime.start.p0i8(i64 4, i8* nonnull %7) #3 101 | %8 = bitcast i32** %4 to i8* 102 | call void @llvm.lifetime.start.p0i8(i64 8, i8* nonnull %8) 103 | store volatile i32* %2, i32** %4, align 8, !tbaa !7 104 | %9 = bitcast i32** %5 to i8* 105 | call void @llvm.lifetime.start.p0i8(i64 8, i8* nonnull %9) 106 | store volatile i32* %3, i32** %5, align 8, !tbaa !7 107 | %10 = add nsw i32 %0, -3 108 | %11 = load volatile i32*, i32** %4, align 8, !tbaa !7 109 | store volatile i32 %10, i32* %11, align 4, !tbaa !3 110 | %12 = load volatile i32*, i32** %4, align 8, !tbaa !7 111 | %13 = load volatile i32*, i32** %5, align 8, !tbaa !7 112 | %14 = icmp eq i32* %12, %13 113 | %15 = load volatile i32*, i32** %4, align 8, !tbaa !7 114 | %16 = load volatile i32, i32* %15, align 4, !tbaa !3 115 | %17 = add nsw i32 %16, -100 116 | %18 = select i1 %14, i32 %17, i32 %16 117 | call void @llvm.lifetime.end.p0i8(i64 8, i8* nonnull %9) 118 | call void @llvm.lifetime.end.p0i8(i64 8, i8* nonnull %8) 119 | call void @llvm.lifetime.end.p0i8(i64 4, i8* nonnull %7) #3 120 | call void @llvm.lifetime.end.p0i8(i64 4, i8* nonnull %6) #3 121 | ret i32 %18 122 | } 123 | 124 | attributes #0 = { nofree norecurse nounwind ssp uwtable "correctly-rounded-divide-sqrt-fp-math"="false" "disable-tail-calls"="false" "less-precise-fpmad"="false" "min-legal-vector-width"="0" "no-frame-pointer-elim"="true" "no-frame-pointer-elim-non-leaf" "no-infs-fp-math"="false" "no-jump-tables"="false" "no-nans-fp-math"="false" "no-signed-zeros-fp-math"="false" "no-trapping-math"="false" "stack-protector-buffer-size"="8" "target-cpu"="penryn" "target-features"="+cx16,+cx8,+fxsr,+mmx,+sahf,+sse,+sse2,+sse3,+sse4.1,+ssse3,+x87" "unsafe-fp-math"="false" "use-soft-float"="false" } 125 | attributes #1 = { nounwind ssp uwtable "correctly-rounded-divide-sqrt-fp-math"="false" "disable-tail-calls"="false" "less-precise-fpmad"="false" "min-legal-vector-width"="0" "no-frame-pointer-elim"="true" "no-frame-pointer-elim-non-leaf" "no-infs-fp-math"="false" "no-jump-tables"="false" "no-nans-fp-math"="false" "no-signed-zeros-fp-math"="false" "no-trapping-math"="false" "stack-protector-buffer-size"="8" "target-cpu"="penryn" "target-features"="+cx16,+cx8,+fxsr,+mmx,+sahf,+sse,+sse2,+sse3,+sse4.1,+ssse3,+x87" "unsafe-fp-math"="false" "use-soft-float"="false" } 126 | attributes #2 = { argmemonly nounwind } 127 | attributes #3 = { nounwind } 128 | 129 | !llvm.module.flags = !{!0, !1} 130 | !llvm.ident = !{!2} 131 | 132 | !0 = !{i32 1, !"wchar_size", i32 4} 133 | !1 = !{i32 7, !"PIC Level", i32 2} 134 | !2 = !{!"clang version 9.0.1 "} 135 | !3 = !{!4, !4, i64 0} 136 | !4 = !{!"int", !5, i64 0} 137 | !5 = !{!"omnipotent char", !6, i64 0} 138 | !6 = !{!"Simple C/C++ TBAA"} 139 | !7 = !{!8, !8, i64 0} 140 | !8 = !{!"any pointer", !5, i64 0} 141 | -------------------------------------------------------------------------------- /tests/throwcatch_tests.rs: -------------------------------------------------------------------------------- 1 | use haybale::solver_utils::PossibleSolutions; 2 | use haybale::*; 3 | 4 | fn init_logging() { 5 | // capture log messages with test harness 6 | let _ = env_logger::builder().is_test(true).try_init(); 7 | } 8 | 9 | fn get_project() -> Project { 10 | let modname = "tests/bcfiles/throwcatch.bc"; 11 | Project::from_bc_path(modname) 12 | .unwrap_or_else(|e| panic!("Failed to parse module {:?}: {}", modname, e)) 13 | } 14 | 15 | #[test] 16 | fn doesnt_throw() { 17 | let funcname = "doesnt_throw"; 18 | init_logging(); 19 | let rvals = get_possible_return_values_of_func( 20 | funcname, 21 | &get_project(), 22 | Config::default(), 23 | None, 24 | Some(32), 25 | 3, 26 | ); 27 | match rvals { 28 | PossibleSolutions::Exactly(hs) => { 29 | for rval in hs { 30 | match rval { 31 | ReturnValue::Return(rval) => assert!(rval > 0), 32 | ReturnValue::ReturnVoid => panic!("Function shouldn't return void"), 33 | ReturnValue::Throw(throwval) => { 34 | panic!("Function shouldn't throw, but it threw {:?}", throwval) 35 | }, 36 | ReturnValue::Abort => panic!("Function shouldn't abort, but it did"), 37 | } 38 | } 39 | }, 40 | PossibleSolutions::AtLeast(hs) => panic!("Too many possible solutions: {:?}", hs), 41 | } 42 | } 43 | 44 | #[test] 45 | fn throw_uncaught() { 46 | let funcname = "throw_uncaught"; 47 | init_logging(); 48 | let rvals = get_possible_return_values_of_func( 49 | funcname, 50 | &get_project(), 51 | Config::default(), 52 | None, 53 | Some(32), 54 | 3, 55 | ); 56 | assert_eq!( 57 | rvals, 58 | PossibleSolutions::exactly_two(ReturnValue::Return(2), ReturnValue::Throw(20)), 59 | ); 60 | } 61 | 62 | #[test] 63 | fn throw_multiple_values() { 64 | let funcname = "throw_multiple_values"; 65 | init_logging(); 66 | let rvals = get_possible_return_values_of_func( 67 | funcname, 68 | &get_project(), 69 | Config::default(), 70 | None, 71 | Some(32), 72 | 5, 73 | ); 74 | assert_eq!( 75 | rvals, 76 | PossibleSolutions::Exactly( 77 | vec![ 78 | ReturnValue::Return(1), 79 | ReturnValue::Return(2), 80 | ReturnValue::Throw(3), 81 | ReturnValue::Throw(4), 82 | ] 83 | .into_iter() 84 | .collect() 85 | ) 86 | ); 87 | } 88 | 89 | #[test] 90 | fn throw_uncaught_wrongtype() { 91 | let funcname = "throw_uncaught_wrongtype"; 92 | init_logging(); 93 | let rvals = get_possible_return_values_of_func( 94 | funcname, 95 | &get_project(), 96 | Config::default(), 97 | None, 98 | Some(32), 99 | 3, 100 | ); 101 | assert_eq!( 102 | rvals, 103 | PossibleSolutions::Exactly( 104 | vec![ 105 | ReturnValue::Return(2), 106 | ReturnValue::Throw(20), 107 | // TODO: This function shouldn't actually be able to Return(10), but 108 | // since our matching of catch blocks is currently imprecise, our 109 | // current symex allows the exception to be either caught or not-caught 110 | ReturnValue::Return(10), 111 | ] 112 | .into_iter() 113 | .collect() 114 | ) 115 | ); 116 | } 117 | 118 | #[test] 119 | fn throw_uncaught_caller() { 120 | let funcname = "throw_uncaught_caller"; 121 | init_logging(); 122 | let rvals = get_possible_return_values_of_func( 123 | funcname, 124 | &get_project(), 125 | Config::default(), 126 | None, 127 | Some(32), 128 | 3, 129 | ); 130 | assert_eq!( 131 | rvals, 132 | PossibleSolutions::exactly_two(ReturnValue::Return(1), ReturnValue::Throw(20)), 133 | ); 134 | } 135 | 136 | #[test] 137 | fn throw_and_catch_wildcard() { 138 | let funcname = "throw_and_catch_wildcard"; 139 | init_logging(); 140 | let rvals = get_possible_return_values_of_func( 141 | funcname, 142 | &get_project(), 143 | Config::default(), 144 | None, 145 | Some(32), 146 | 3, 147 | ); 148 | assert_eq!( 149 | rvals, 150 | PossibleSolutions::exactly_two(ReturnValue::Return(2), ReturnValue::Return(5)), 151 | ); 152 | } 153 | 154 | #[test] 155 | fn throw_and_catch_val() { 156 | let funcname = "throw_and_catch_val"; 157 | init_logging(); 158 | let rvals = get_possible_return_values_of_func( 159 | funcname, 160 | &get_project(), 161 | Config::default(), 162 | None, 163 | Some(32), 164 | 3, 165 | ); 166 | assert_eq!( 167 | rvals, 168 | PossibleSolutions::Exactly( 169 | vec![ 170 | ReturnValue::Return(2), 171 | ReturnValue::Return(20), 172 | // TODO: This function shouldn't actually be able to Throw(20), but 173 | // since our matching of catch blocks is currently imprecise, our 174 | // current symex allows the exception to be either caught or not-caught 175 | ReturnValue::Throw(20), 176 | ] 177 | .into_iter() 178 | .collect() 179 | ) 180 | ); 181 | } 182 | 183 | #[test] 184 | fn throw_and_catch_in_caller() { 185 | let funcname = "throw_and_catch_in_caller"; 186 | init_logging(); 187 | let rvals = get_possible_return_values_of_func( 188 | funcname, 189 | &get_project(), 190 | Config::default(), 191 | None, 192 | Some(32), 193 | 3, 194 | ); 195 | assert_eq!( 196 | rvals, 197 | PossibleSolutions::Exactly( 198 | vec![ 199 | ReturnValue::Return(2), 200 | ReturnValue::Return(20), 201 | // TODO: This function shouldn't actually be able to Throw(20), but 202 | // since our matching of catch blocks is currently imprecise, our 203 | // current symex allows the exception to be either caught or not-caught 204 | ReturnValue::Throw(20), 205 | ] 206 | .into_iter() 207 | .collect() 208 | ) 209 | ); 210 | } 211 | 212 | #[test] 213 | // TODO: We don't currently support __cxa_rethrow 214 | #[should_panic(expected = "__cxa_rethrow")] 215 | fn throw_and_rethrow_in_caller() { 216 | let funcname = "throw_and_rethrow_in_caller"; 217 | init_logging(); 218 | let rvals = get_possible_return_values_of_func( 219 | funcname, 220 | &get_project(), 221 | Config::default(), 222 | None, 223 | Some(32), 224 | 3, 225 | ); 226 | assert_eq!( 227 | rvals, 228 | PossibleSolutions::exactly_two(ReturnValue::Return(2), ReturnValue::Throw(20)), 229 | ); 230 | } 231 | -------------------------------------------------------------------------------- /tests/bcfiles/basic.ll: -------------------------------------------------------------------------------- 1 | ; ModuleID = 'basic.c' 2 | source_filename = "basic.c" 3 | target datalayout = "e-m:o-i64:64-f80:128-n8:16:32:64-S128" 4 | target triple = "x86_64-apple-macosx10.15.0" 5 | 6 | ; Function Attrs: norecurse nounwind readnone ssp uwtable 7 | define i32 @no_args_zero() local_unnamed_addr #0 { 8 | ret i32 0 9 | } 10 | 11 | ; Function Attrs: norecurse nounwind readnone ssp uwtable 12 | define i32 @no_args_nozero() local_unnamed_addr #0 { 13 | ret i32 1 14 | } 15 | 16 | ; Function Attrs: norecurse nounwind readnone ssp uwtable 17 | define i32 @one_arg(i32) local_unnamed_addr #0 { 18 | %2 = add nsw i32 %0, -3 19 | ret i32 %2 20 | } 21 | 22 | ; Function Attrs: norecurse nounwind readnone ssp uwtable 23 | define i32 @two_args(i32, i32) local_unnamed_addr #0 { 24 | %3 = add i32 %0, -3 25 | %4 = add i32 %3, %1 26 | ret i32 %4 27 | } 28 | 29 | ; Function Attrs: norecurse nounwind readnone ssp uwtable 30 | define i32 @three_args(i32, i32, i32) local_unnamed_addr #0 { 31 | %4 = add i32 %0, -3 32 | %5 = add i32 %4, %1 33 | %6 = add i32 %5, %2 34 | ret i32 %6 35 | } 36 | 37 | ; Function Attrs: norecurse nounwind readnone ssp uwtable 38 | define i32 @four_args(i32, i32, i32, i32) local_unnamed_addr #0 { 39 | %5 = add i32 %0, -3 40 | %6 = add i32 %5, %1 41 | %7 = add i32 %6, %2 42 | %8 = add i32 %7, %3 43 | ret i32 %8 44 | } 45 | 46 | ; Function Attrs: norecurse nounwind readnone ssp uwtable 47 | define i32 @five_args(i32, i32, i32, i32, i32) local_unnamed_addr #0 { 48 | %6 = add i32 %0, -3 49 | %7 = add i32 %6, %1 50 | %8 = add i32 %7, %2 51 | %9 = add i32 %8, %3 52 | %10 = add i32 %9, %4 53 | ret i32 %10 54 | } 55 | 56 | ; Function Attrs: norecurse nounwind readnone ssp uwtable 57 | define i32 @binops(i32, i32) local_unnamed_addr #0 { 58 | %3 = mul i32 %0, -77 59 | %4 = add i32 %0, 1 60 | %5 = add i32 %4, %1 61 | %6 = add i32 %5, %3 62 | %7 = and i32 %6, 23 63 | %8 = or i32 %0, 99 64 | %9 = sdiv i32 %7, %8 65 | %10 = xor i32 %9, %0 66 | %11 = shl i32 %6, 3 67 | %12 = srem i32 %10, %11 68 | %13 = ashr i32 %12, %9 69 | ret i32 %13 70 | } 71 | 72 | ; Function Attrs: norecurse nounwind readnone ssp uwtable 73 | define i32 @conditional_true(i32, i32) local_unnamed_addr #0 { 74 | %3 = icmp sgt i32 %0, %1 75 | br i1 %3, label %4, label %8 76 | 77 | 4: ; preds = %2 78 | %5 = add nsw i32 %0, -1 79 | %6 = add nsw i32 %1, -1 80 | %7 = mul nsw i32 %6, %5 81 | br label %12 82 | 83 | 8: ; preds = %2 84 | %9 = add nsw i32 %1, %0 85 | %10 = srem i32 %9, 3 86 | %11 = add nsw i32 %10, 10 87 | br label %12 88 | 89 | 12: ; preds = %8, %4 90 | %13 = phi i32 [ %7, %4 ], [ %11, %8 ] 91 | ret i32 %13 92 | } 93 | 94 | ; Function Attrs: norecurse nounwind readnone ssp uwtable 95 | define i32 @conditional_false(i32, i32) local_unnamed_addr #0 { 96 | %3 = icmp sgt i32 %0, %1 97 | br i1 %3, label %4, label %8 98 | 99 | 4: ; preds = %2 100 | %5 = add nsw i32 %1, %0 101 | %6 = srem i32 %5, 3 102 | %7 = add nsw i32 %6, 10 103 | br label %12 104 | 105 | 8: ; preds = %2 106 | %9 = add nsw i32 %0, -1 107 | %10 = add nsw i32 %1, -1 108 | %11 = mul nsw i32 %10, %9 109 | br label %12 110 | 111 | 12: ; preds = %8, %4 112 | %13 = phi i32 [ %7, %4 ], [ %11, %8 ] 113 | ret i32 %13 114 | } 115 | 116 | ; Function Attrs: norecurse nounwind readnone ssp uwtable 117 | define i32 @conditional_nozero(i32, i32) local_unnamed_addr #0 { 118 | %3 = icmp sgt i32 %0, 2 119 | br i1 %3, label %14, label %4 120 | 121 | 4: ; preds = %2 122 | %5 = icmp slt i32 %1, 1 123 | br i1 %5, label %6, label %8 124 | 125 | 6: ; preds = %4 126 | %7 = add nsw i32 %1, -3 127 | br label %14 128 | 129 | 8: ; preds = %4 130 | %9 = icmp slt i32 %0, 1 131 | br i1 %9, label %10, label %12 132 | 133 | 10: ; preds = %8 134 | %11 = add nsw i32 %0, -7 135 | br label %14 136 | 137 | 12: ; preds = %8 138 | %13 = mul nsw i32 %1, %0 139 | br label %14 140 | 141 | 14: ; preds = %2, %12, %10, %6 142 | %15 = phi i32 [ %7, %6 ], [ %11, %10 ], [ %13, %12 ], [ %0, %2 ] 143 | ret i32 %15 144 | } 145 | 146 | ; Function Attrs: norecurse nounwind readnone ssp uwtable 147 | define i32 @conditional_with_and(i32, i32) local_unnamed_addr #0 { 148 | %3 = icmp slt i32 %0, 4 149 | %4 = icmp slt i32 %1, 5 150 | %5 = or i1 %3, %4 151 | %6 = zext i1 %5 to i32 152 | ret i32 %6 153 | } 154 | 155 | ; Function Attrs: norecurse nounwind readnone ssp uwtable 156 | define i32 @has_switch(i32, i32) local_unnamed_addr #0 { 157 | %3 = sub nsw i32 %0, %1 158 | switch i32 %3, label %12 [ 159 | i32 0, label %14 160 | i32 1, label %4 161 | i32 2, label %5 162 | i32 3, label %7 163 | i32 33, label %10 164 | i32 451, label %11 165 | ] 166 | 167 | 4: ; preds = %2 168 | br label %14 169 | 170 | 5: ; preds = %2 171 | %6 = add nsw i32 %0, -3 172 | br label %14 173 | 174 | 7: ; preds = %2 175 | %8 = mul nsw i32 %1, %0 176 | %9 = add nsw i32 %8, 1 177 | br label %14 178 | 179 | 10: ; preds = %2 180 | br label %14 181 | 182 | 11: ; preds = %2 183 | br label %14 184 | 185 | 12: ; preds = %2 186 | %13 = add nsw i32 %3, -1 187 | br label %14 188 | 189 | 14: ; preds = %2, %12, %11, %10, %7, %5, %4 190 | %15 = phi i32 [ %13, %12 ], [ -5, %11 ], [ -300, %10 ], [ %9, %7 ], [ %6, %5 ], [ 3, %4 ], [ -1, %2 ] 191 | ret i32 %15 192 | } 193 | 194 | ; Function Attrs: norecurse nounwind readnone ssp uwtable 195 | define signext i8 @int8t(i8 signext, i8 signext) local_unnamed_addr #0 { 196 | %3 = add i8 %0, -3 197 | %4 = add i8 %3, %1 198 | ret i8 %4 199 | } 200 | 201 | ; Function Attrs: norecurse nounwind readnone ssp uwtable 202 | define signext i16 @int16t(i16 signext, i16 signext) local_unnamed_addr #0 { 203 | %3 = add i16 %0, -3 204 | %4 = add i16 %3, %1 205 | ret i16 %4 206 | } 207 | 208 | ; Function Attrs: norecurse nounwind readnone ssp uwtable 209 | define i32 @int32t(i32, i32) local_unnamed_addr #0 { 210 | %3 = add i32 %0, -3 211 | %4 = add i32 %3, %1 212 | ret i32 %4 213 | } 214 | 215 | ; Function Attrs: norecurse nounwind readnone ssp uwtable 216 | define i64 @int64t(i64, i64) local_unnamed_addr #0 { 217 | %3 = add i64 %0, -3 218 | %4 = add i64 %3, %1 219 | ret i64 %4 220 | } 221 | 222 | ; Function Attrs: norecurse nounwind readnone ssp uwtable 223 | define i64 @mixed_bitwidths(i8 signext, i16 signext, i32, i64) local_unnamed_addr #0 { 224 | %5 = sext i8 %0 to i32 225 | %6 = sext i16 %1 to i32 226 | %7 = add nsw i32 %6, %5 227 | %8 = add nsw i32 %7, %2 228 | %9 = sext i32 %8 to i64 229 | %10 = add i64 %3, -3 230 | %11 = add i64 %10, %9 231 | ret i64 %11 232 | } 233 | 234 | attributes #0 = { norecurse nounwind readnone ssp uwtable "correctly-rounded-divide-sqrt-fp-math"="false" "disable-tail-calls"="false" "less-precise-fpmad"="false" "min-legal-vector-width"="0" "no-frame-pointer-elim"="true" "no-frame-pointer-elim-non-leaf" "no-infs-fp-math"="false" "no-jump-tables"="false" "no-nans-fp-math"="false" "no-signed-zeros-fp-math"="false" "no-trapping-math"="false" "stack-protector-buffer-size"="8" "target-cpu"="penryn" "target-features"="+cx16,+cx8,+fxsr,+mmx,+sahf,+sse,+sse2,+sse3,+sse4.1,+ssse3,+x87" "unsafe-fp-math"="false" "use-soft-float"="false" } 235 | 236 | !llvm.module.flags = !{!0, !1} 237 | !llvm.ident = !{!2} 238 | 239 | !0 = !{i32 1, !"wchar_size", i32 4} 240 | !1 = !{i32 7, !"PIC Level", i32 2} 241 | !2 = !{!"clang version 9.0.1 "} 242 | -------------------------------------------------------------------------------- /tests/bcfiles/simd.ll: -------------------------------------------------------------------------------- 1 | ; ModuleID = 'simd.c' 2 | source_filename = "simd.c" 3 | target datalayout = "e-m:o-i64:64-f80:128-n8:16:32:64-S128" 4 | target triple = "x86_64-apple-macosx10.15.0" 5 | 6 | ; Function Attrs: nofree noinline norecurse nounwind ssp uwtable 7 | define i32 @callee(i32* nocapture, i32* nocapture, i32* noalias nocapture) local_unnamed_addr #0 { 8 | store i32 0, i32* %0, align 4, !tbaa !3 9 | store i32 2, i32* %1, align 4, !tbaa !3 10 | %4 = getelementptr inbounds i32, i32* %0, i64 1 11 | store i32 1, i32* %4, align 4, !tbaa !3 12 | %5 = getelementptr inbounds i32, i32* %1, i64 1 13 | store i32 3, i32* %5, align 4, !tbaa !3 14 | %6 = getelementptr inbounds i32, i32* %0, i64 2 15 | store i32 2, i32* %6, align 4, !tbaa !3 16 | %7 = getelementptr inbounds i32, i32* %1, i64 2 17 | store i32 4, i32* %7, align 4, !tbaa !3 18 | %8 = getelementptr inbounds i32, i32* %0, i64 3 19 | store i32 3, i32* %8, align 4, !tbaa !3 20 | %9 = getelementptr inbounds i32, i32* %1, i64 3 21 | store i32 5, i32* %9, align 4, !tbaa !3 22 | %10 = getelementptr inbounds i32, i32* %0, i64 4 23 | store i32 4, i32* %10, align 4, !tbaa !3 24 | %11 = getelementptr inbounds i32, i32* %1, i64 4 25 | store i32 6, i32* %11, align 4, !tbaa !3 26 | %12 = getelementptr inbounds i32, i32* %0, i64 5 27 | store i32 5, i32* %12, align 4, !tbaa !3 28 | %13 = getelementptr inbounds i32, i32* %1, i64 5 29 | store i32 7, i32* %13, align 4, !tbaa !3 30 | %14 = getelementptr inbounds i32, i32* %0, i64 6 31 | store i32 6, i32* %14, align 4, !tbaa !3 32 | %15 = getelementptr inbounds i32, i32* %1, i64 6 33 | store i32 8, i32* %15, align 4, !tbaa !3 34 | %16 = getelementptr inbounds i32, i32* %0, i64 7 35 | store i32 7, i32* %16, align 4, !tbaa !3 36 | %17 = getelementptr inbounds i32, i32* %1, i64 7 37 | store i32 9, i32* %17, align 4, !tbaa !3 38 | %18 = getelementptr inbounds i32, i32* %0, i64 8 39 | store i32 8, i32* %18, align 4, !tbaa !3 40 | %19 = getelementptr inbounds i32, i32* %1, i64 8 41 | store i32 10, i32* %19, align 4, !tbaa !3 42 | %20 = getelementptr inbounds i32, i32* %0, i64 9 43 | store i32 9, i32* %20, align 4, !tbaa !3 44 | %21 = getelementptr inbounds i32, i32* %1, i64 9 45 | store i32 11, i32* %21, align 4, !tbaa !3 46 | %22 = getelementptr inbounds i32, i32* %0, i64 10 47 | store i32 10, i32* %22, align 4, !tbaa !3 48 | %23 = getelementptr inbounds i32, i32* %1, i64 10 49 | store i32 12, i32* %23, align 4, !tbaa !3 50 | %24 = getelementptr inbounds i32, i32* %0, i64 11 51 | store i32 11, i32* %24, align 4, !tbaa !3 52 | %25 = getelementptr inbounds i32, i32* %1, i64 11 53 | store i32 13, i32* %25, align 4, !tbaa !3 54 | %26 = getelementptr inbounds i32, i32* %0, i64 12 55 | store i32 12, i32* %26, align 4, !tbaa !3 56 | %27 = getelementptr inbounds i32, i32* %1, i64 12 57 | store i32 14, i32* %27, align 4, !tbaa !3 58 | %28 = getelementptr inbounds i32, i32* %0, i64 13 59 | store i32 13, i32* %28, align 4, !tbaa !3 60 | %29 = getelementptr inbounds i32, i32* %1, i64 13 61 | store i32 15, i32* %29, align 4, !tbaa !3 62 | %30 = getelementptr inbounds i32, i32* %0, i64 14 63 | store i32 14, i32* %30, align 4, !tbaa !3 64 | %31 = getelementptr inbounds i32, i32* %1, i64 14 65 | store i32 16, i32* %31, align 4, !tbaa !3 66 | %32 = getelementptr inbounds i32, i32* %0, i64 15 67 | store i32 15, i32* %32, align 4, !tbaa !3 68 | %33 = getelementptr inbounds i32, i32* %1, i64 15 69 | store i32 17, i32* %33, align 4, !tbaa !3 70 | %34 = bitcast i32* %0 to <4 x i32>* 71 | %35 = load <4 x i32>, <4 x i32>* %34, align 4, !tbaa !3 72 | %36 = bitcast i32* %1 to <4 x i32>* 73 | %37 = load <4 x i32>, <4 x i32>* %36, align 4, !tbaa !3 74 | %38 = add <4 x i32> %37, %35 75 | %39 = bitcast i32* %2 to <4 x i32>* 76 | store <4 x i32> %38, <4 x i32>* %39, align 4, !tbaa !3 77 | %40 = getelementptr inbounds i32, i32* %2, i64 4 78 | %41 = bitcast i32* %10 to <4 x i32>* 79 | %42 = load <4 x i32>, <4 x i32>* %41, align 4, !tbaa !3 80 | %43 = bitcast i32* %11 to <4 x i32>* 81 | %44 = load <4 x i32>, <4 x i32>* %43, align 4, !tbaa !3 82 | %45 = add <4 x i32> %44, %42 83 | %46 = bitcast i32* %40 to <4 x i32>* 84 | store <4 x i32> %45, <4 x i32>* %46, align 4, !tbaa !3 85 | %47 = getelementptr inbounds i32, i32* %2, i64 8 86 | %48 = bitcast i32* %18 to <4 x i32>* 87 | %49 = load <4 x i32>, <4 x i32>* %48, align 4, !tbaa !3 88 | %50 = bitcast i32* %19 to <4 x i32>* 89 | %51 = load <4 x i32>, <4 x i32>* %50, align 4, !tbaa !3 90 | %52 = add <4 x i32> %51, %49 91 | %53 = bitcast i32* %47 to <4 x i32>* 92 | store <4 x i32> %52, <4 x i32>* %53, align 4, !tbaa !3 93 | %54 = getelementptr inbounds i32, i32* %2, i64 12 94 | %55 = bitcast i32* %27 to <2 x i32>* 95 | %56 = load <2 x i32>, <2 x i32>* %55, align 4, !tbaa !3 96 | %57 = load i32, i32* %31, align 4, !tbaa !3 97 | %58 = bitcast i32* %26 to <4 x i32>* 98 | %59 = load <4 x i32>, <4 x i32>* %58, align 4, !tbaa !3 99 | %60 = extractelement <2 x i32> %56, i32 0 100 | %61 = extractelement <2 x i32> %56, i32 1 101 | %62 = insertelement <4 x i32> , i32 %60, i32 0 102 | %63 = insertelement <4 x i32> %62, i32 %61, i32 1 103 | %64 = insertelement <4 x i32> %63, i32 %57, i32 2 104 | %65 = add <4 x i32> %64, %59 105 | %66 = bitcast i32* %54 to <4 x i32>* 106 | store <4 x i32> %65, <4 x i32>* %66, align 4, !tbaa !3 107 | %67 = extractelement <4 x i32> %38, i32 0 108 | %68 = extractelement <4 x i32> %38, i32 1 109 | %69 = add i32 %68, %67 110 | %70 = extractelement <4 x i32> %38, i32 2 111 | %71 = add i32 %70, %69 112 | %72 = extractelement <4 x i32> %38, i32 3 113 | %73 = add i32 %72, %71 114 | %74 = extractelement <4 x i32> %45, i32 0 115 | %75 = add i32 %74, %73 116 | %76 = extractelement <4 x i32> %45, i32 1 117 | %77 = add i32 %76, %75 118 | %78 = extractelement <4 x i32> %45, i32 2 119 | %79 = add i32 %78, %77 120 | %80 = extractelement <4 x i32> %45, i32 3 121 | %81 = add i32 %80, %79 122 | %82 = extractelement <4 x i32> %52, i32 0 123 | %83 = add i32 %82, %81 124 | %84 = extractelement <4 x i32> %52, i32 1 125 | %85 = add i32 %84, %83 126 | %86 = extractelement <4 x i32> %52, i32 2 127 | %87 = add i32 %86, %85 128 | %88 = extractelement <4 x i32> %52, i32 3 129 | %89 = add i32 %88, %87 130 | %90 = extractelement <4 x i32> %65, i32 0 131 | %91 = add i32 %90, %89 132 | %92 = extractelement <4 x i32> %65, i32 1 133 | %93 = add i32 %92, %91 134 | %94 = extractelement <4 x i32> %65, i32 2 135 | %95 = add i32 %94, %93 136 | %96 = extractelement <4 x i32> %65, i32 3 137 | %97 = add i32 %96, %95 138 | ret i32 %97 139 | } 140 | 141 | ; Function Attrs: argmemonly nounwind 142 | declare void @llvm.lifetime.start.p0i8(i64 immarg, i8* nocapture) #1 143 | 144 | ; Function Attrs: argmemonly nounwind 145 | declare void @llvm.lifetime.end.p0i8(i64 immarg, i8* nocapture) #1 146 | 147 | ; Function Attrs: nounwind ssp uwtable 148 | define i32 @simd_add_autovectorized() local_unnamed_addr #2 { 149 | %1 = alloca [16 x i32], align 16 150 | %2 = alloca [16 x i32], align 16 151 | %3 = alloca [16 x i32], align 16 152 | %4 = bitcast [16 x i32]* %1 to i8* 153 | call void @llvm.lifetime.start.p0i8(i64 64, i8* nonnull %4) #3 154 | %5 = bitcast [16 x i32]* %2 to i8* 155 | call void @llvm.lifetime.start.p0i8(i64 64, i8* nonnull %5) #3 156 | %6 = bitcast [16 x i32]* %3 to i8* 157 | call void @llvm.lifetime.start.p0i8(i64 64, i8* nonnull %6) #3 158 | %7 = getelementptr inbounds [16 x i32], [16 x i32]* %1, i64 0, i64 0 159 | %8 = getelementptr inbounds [16 x i32], [16 x i32]* %2, i64 0, i64 0 160 | %9 = getelementptr inbounds [16 x i32], [16 x i32]* %3, i64 0, i64 0 161 | %10 = call i32 @callee(i32* nonnull %7, i32* nonnull %8, i32* nonnull %9) 162 | call void @llvm.lifetime.end.p0i8(i64 64, i8* nonnull %6) #3 163 | call void @llvm.lifetime.end.p0i8(i64 64, i8* nonnull %5) #3 164 | call void @llvm.lifetime.end.p0i8(i64 64, i8* nonnull %4) #3 165 | ret i32 %10 166 | } 167 | 168 | attributes #0 = { nofree noinline norecurse nounwind ssp uwtable "correctly-rounded-divide-sqrt-fp-math"="false" "disable-tail-calls"="false" "less-precise-fpmad"="false" "min-legal-vector-width"="0" "no-frame-pointer-elim"="true" "no-frame-pointer-elim-non-leaf" "no-infs-fp-math"="false" "no-jump-tables"="false" "no-nans-fp-math"="false" "no-signed-zeros-fp-math"="false" "no-trapping-math"="false" "stack-protector-buffer-size"="8" "target-cpu"="penryn" "target-features"="+cx16,+cx8,+fxsr,+mmx,+sahf,+sse,+sse2,+sse3,+sse4.1,+ssse3,+x87" "unsafe-fp-math"="false" "use-soft-float"="false" } 169 | attributes #1 = { argmemonly nounwind } 170 | attributes #2 = { nounwind ssp uwtable "correctly-rounded-divide-sqrt-fp-math"="false" "disable-tail-calls"="false" "less-precise-fpmad"="false" "min-legal-vector-width"="0" "no-frame-pointer-elim"="true" "no-frame-pointer-elim-non-leaf" "no-infs-fp-math"="false" "no-jump-tables"="false" "no-nans-fp-math"="false" "no-signed-zeros-fp-math"="false" "no-trapping-math"="false" "stack-protector-buffer-size"="8" "target-cpu"="penryn" "target-features"="+cx16,+cx8,+fxsr,+mmx,+sahf,+sse,+sse2,+sse3,+sse4.1,+ssse3,+x87" "unsafe-fp-math"="false" "use-soft-float"="false" } 171 | attributes #3 = { nounwind } 172 | 173 | !llvm.module.flags = !{!0, !1} 174 | !llvm.ident = !{!2} 175 | 176 | !0 = !{i32 1, !"wchar_size", i32 4} 177 | !1 = !{i32 7, !"PIC Level", i32 2} 178 | !2 = !{!"clang version 9.0.1 "} 179 | !3 = !{!4, !4, i64 0} 180 | !4 = !{!"int", !5, i64 0} 181 | !5 = !{!"omnipotent char", !6, i64 0} 182 | !6 = !{!"Simple C/C++ TBAA"} 183 | -------------------------------------------------------------------------------- /tests/bcfiles/linkedlist.ll: -------------------------------------------------------------------------------- 1 | ; ModuleID = 'linkedlist.c' 2 | source_filename = "linkedlist.c" 3 | target datalayout = "e-m:o-i64:64-f80:128-n8:16:32:64-S128" 4 | target triple = "x86_64-apple-macosx10.15.0" 5 | 6 | %struct.SimpleLinkedList = type { i32, %struct.SimpleLinkedList* } 7 | %struct.NodeA = type { i32, %struct.NodeB* } 8 | %struct.NodeB = type { i32, %struct.NodeA* } 9 | 10 | ; Function Attrs: noinline nounwind optnone ssp uwtable 11 | define i32 @simple_linked_list(i32) #0 { 12 | %2 = alloca i32, align 4 13 | %3 = alloca %struct.SimpleLinkedList, align 8 14 | %4 = alloca %struct.SimpleLinkedList, align 8 15 | %5 = alloca %struct.SimpleLinkedList, align 8 16 | %6 = alloca %struct.SimpleLinkedList, align 8 17 | %7 = alloca %struct.SimpleLinkedList, align 8 18 | store i32 %0, i32* %2, align 4 19 | %8 = getelementptr inbounds %struct.SimpleLinkedList, %struct.SimpleLinkedList* %3, i32 0, i32 0 20 | %9 = load i32, i32* %2, align 4 21 | store i32 %9, i32* %8, align 8 22 | %10 = getelementptr inbounds %struct.SimpleLinkedList, %struct.SimpleLinkedList* %3, i32 0, i32 1 23 | store %struct.SimpleLinkedList* null, %struct.SimpleLinkedList** %10, align 8 24 | %11 = getelementptr inbounds %struct.SimpleLinkedList, %struct.SimpleLinkedList* %3, i32 0, i32 0 25 | %12 = load i32, i32* %11, align 8 26 | %13 = add nsw i32 %12, 2 27 | store i32 %13, i32* %11, align 8 28 | %14 = getelementptr inbounds %struct.SimpleLinkedList, %struct.SimpleLinkedList* %4, i32 0, i32 0 29 | %15 = load i32, i32* %2, align 4 30 | %16 = sub nsw i32 %15, 3 31 | store i32 %16, i32* %14, align 8 32 | %17 = getelementptr inbounds %struct.SimpleLinkedList, %struct.SimpleLinkedList* %4, i32 0, i32 1 33 | store %struct.SimpleLinkedList* null, %struct.SimpleLinkedList** %17, align 8 34 | %18 = getelementptr inbounds %struct.SimpleLinkedList, %struct.SimpleLinkedList* %5, i32 0, i32 0 35 | %19 = load i32, i32* %2, align 4 36 | %20 = mul nsw i32 %19, 5 37 | store i32 %20, i32* %18, align 8 38 | %21 = getelementptr inbounds %struct.SimpleLinkedList, %struct.SimpleLinkedList* %5, i32 0, i32 1 39 | store %struct.SimpleLinkedList* null, %struct.SimpleLinkedList** %21, align 8 40 | %22 = getelementptr inbounds %struct.SimpleLinkedList, %struct.SimpleLinkedList* %6, i32 0, i32 0 41 | %23 = load i32, i32* %2, align 4 42 | %24 = sdiv i32 %23, 2 43 | store i32 %24, i32* %22, align 8 44 | %25 = getelementptr inbounds %struct.SimpleLinkedList, %struct.SimpleLinkedList* %6, i32 0, i32 1 45 | store %struct.SimpleLinkedList* null, %struct.SimpleLinkedList** %25, align 8 46 | %26 = getelementptr inbounds %struct.SimpleLinkedList, %struct.SimpleLinkedList* %7, i32 0, i32 0 47 | %27 = load i32, i32* %2, align 4 48 | %28 = sdiv i32 %27, 100 49 | store i32 %28, i32* %26, align 8 50 | %29 = getelementptr inbounds %struct.SimpleLinkedList, %struct.SimpleLinkedList* %7, i32 0, i32 1 51 | store %struct.SimpleLinkedList* null, %struct.SimpleLinkedList** %29, align 8 52 | %30 = getelementptr inbounds %struct.SimpleLinkedList, %struct.SimpleLinkedList* %3, i32 0, i32 1 53 | store %struct.SimpleLinkedList* %4, %struct.SimpleLinkedList** %30, align 8 54 | %31 = getelementptr inbounds %struct.SimpleLinkedList, %struct.SimpleLinkedList* %4, i32 0, i32 1 55 | store %struct.SimpleLinkedList* %5, %struct.SimpleLinkedList** %31, align 8 56 | %32 = getelementptr inbounds %struct.SimpleLinkedList, %struct.SimpleLinkedList* %5, i32 0, i32 1 57 | store %struct.SimpleLinkedList* %6, %struct.SimpleLinkedList** %32, align 8 58 | %33 = getelementptr inbounds %struct.SimpleLinkedList, %struct.SimpleLinkedList* %6, i32 0, i32 1 59 | store %struct.SimpleLinkedList* %7, %struct.SimpleLinkedList** %33, align 8 60 | %34 = getelementptr inbounds %struct.SimpleLinkedList, %struct.SimpleLinkedList* %7, i32 0, i32 1 61 | store %struct.SimpleLinkedList* %3, %struct.SimpleLinkedList** %34, align 8 62 | %35 = getelementptr inbounds %struct.SimpleLinkedList, %struct.SimpleLinkedList* %3, i32 0, i32 1 63 | %36 = load %struct.SimpleLinkedList*, %struct.SimpleLinkedList** %35, align 8 64 | %37 = getelementptr inbounds %struct.SimpleLinkedList, %struct.SimpleLinkedList* %36, i32 0, i32 1 65 | %38 = load %struct.SimpleLinkedList*, %struct.SimpleLinkedList** %37, align 8 66 | %39 = getelementptr inbounds %struct.SimpleLinkedList, %struct.SimpleLinkedList* %38, i32 0, i32 1 67 | %40 = load %struct.SimpleLinkedList*, %struct.SimpleLinkedList** %39, align 8 68 | %41 = getelementptr inbounds %struct.SimpleLinkedList, %struct.SimpleLinkedList* %40, i32 0, i32 1 69 | %42 = load %struct.SimpleLinkedList*, %struct.SimpleLinkedList** %41, align 8 70 | %43 = getelementptr inbounds %struct.SimpleLinkedList, %struct.SimpleLinkedList* %42, i32 0, i32 1 71 | %44 = load %struct.SimpleLinkedList*, %struct.SimpleLinkedList** %43, align 8 72 | %45 = getelementptr inbounds %struct.SimpleLinkedList, %struct.SimpleLinkedList* %44, i32 0, i32 1 73 | %46 = load %struct.SimpleLinkedList*, %struct.SimpleLinkedList** %45, align 8 74 | %47 = getelementptr inbounds %struct.SimpleLinkedList, %struct.SimpleLinkedList* %46, i32 0, i32 1 75 | %48 = load %struct.SimpleLinkedList*, %struct.SimpleLinkedList** %47, align 8 76 | %49 = getelementptr inbounds %struct.SimpleLinkedList, %struct.SimpleLinkedList* %48, i32 0, i32 1 77 | %50 = load %struct.SimpleLinkedList*, %struct.SimpleLinkedList** %49, align 8 78 | %51 = getelementptr inbounds %struct.SimpleLinkedList, %struct.SimpleLinkedList* %50, i32 0, i32 1 79 | %52 = load %struct.SimpleLinkedList*, %struct.SimpleLinkedList** %51, align 8 80 | %53 = getelementptr inbounds %struct.SimpleLinkedList, %struct.SimpleLinkedList* %52, i32 0, i32 1 81 | %54 = load %struct.SimpleLinkedList*, %struct.SimpleLinkedList** %53, align 8 82 | %55 = getelementptr inbounds %struct.SimpleLinkedList, %struct.SimpleLinkedList* %54, i32 0, i32 1 83 | %56 = load %struct.SimpleLinkedList*, %struct.SimpleLinkedList** %55, align 8 84 | %57 = getelementptr inbounds %struct.SimpleLinkedList, %struct.SimpleLinkedList* %56, i32 0, i32 0 85 | %58 = load i32, i32* %57, align 8 86 | ret i32 %58 87 | } 88 | 89 | ; Function Attrs: noinline nounwind optnone ssp uwtable 90 | define i32 @indirectly_recursive_type(i32) #0 { 91 | %2 = alloca i32, align 4 92 | %3 = alloca %struct.NodeA, align 8 93 | %4 = alloca %struct.NodeB, align 8 94 | %5 = alloca %struct.NodeA, align 8 95 | store i32 %0, i32* %2, align 4 96 | %6 = getelementptr inbounds %struct.NodeA, %struct.NodeA* %3, i32 0, i32 0 97 | %7 = load i32, i32* %2, align 4 98 | store i32 %7, i32* %6, align 8 99 | %8 = getelementptr inbounds %struct.NodeA, %struct.NodeA* %3, i32 0, i32 1 100 | store %struct.NodeB* null, %struct.NodeB** %8, align 8 101 | %9 = getelementptr inbounds %struct.NodeB, %struct.NodeB* %4, i32 0, i32 0 102 | %10 = load i32, i32* %2, align 4 103 | %11 = sub nsw i32 %10, 3 104 | store i32 %11, i32* %9, align 8 105 | %12 = getelementptr inbounds %struct.NodeB, %struct.NodeB* %4, i32 0, i32 1 106 | store %struct.NodeA* null, %struct.NodeA** %12, align 8 107 | %13 = getelementptr inbounds %struct.NodeA, %struct.NodeA* %5, i32 0, i32 0 108 | %14 = load i32, i32* %2, align 4 109 | %15 = sdiv i32 %14, 4 110 | store i32 %15, i32* %13, align 8 111 | %16 = getelementptr inbounds %struct.NodeA, %struct.NodeA* %5, i32 0, i32 1 112 | store %struct.NodeB* null, %struct.NodeB** %16, align 8 113 | %17 = getelementptr inbounds %struct.NodeA, %struct.NodeA* %3, i32 0, i32 1 114 | store %struct.NodeB* %4, %struct.NodeB** %17, align 8 115 | %18 = getelementptr inbounds %struct.NodeB, %struct.NodeB* %4, i32 0, i32 1 116 | store %struct.NodeA* %5, %struct.NodeA** %18, align 8 117 | %19 = getelementptr inbounds %struct.NodeA, %struct.NodeA* %5, i32 0, i32 1 118 | store %struct.NodeB* %4, %struct.NodeB** %19, align 8 119 | %20 = getelementptr inbounds %struct.NodeA, %struct.NodeA* %3, i32 0, i32 1 120 | %21 = load %struct.NodeB*, %struct.NodeB** %20, align 8 121 | %22 = getelementptr inbounds %struct.NodeB, %struct.NodeB* %21, i32 0, i32 1 122 | %23 = load %struct.NodeA*, %struct.NodeA** %22, align 8 123 | %24 = getelementptr inbounds %struct.NodeA, %struct.NodeA* %23, i32 0, i32 1 124 | %25 = load %struct.NodeB*, %struct.NodeB** %24, align 8 125 | %26 = getelementptr inbounds %struct.NodeB, %struct.NodeB* %25, i32 0, i32 1 126 | %27 = load %struct.NodeA*, %struct.NodeA** %26, align 8 127 | %28 = getelementptr inbounds %struct.NodeA, %struct.NodeA* %27, i32 0, i32 1 128 | %29 = load %struct.NodeB*, %struct.NodeB** %28, align 8 129 | %30 = getelementptr inbounds %struct.NodeB, %struct.NodeB* %29, i32 0, i32 1 130 | %31 = load %struct.NodeA*, %struct.NodeA** %30, align 8 131 | %32 = getelementptr inbounds %struct.NodeA, %struct.NodeA* %31, i32 0, i32 1 132 | %33 = load %struct.NodeB*, %struct.NodeB** %32, align 8 133 | %34 = getelementptr inbounds %struct.NodeB, %struct.NodeB* %33, i32 0, i32 0 134 | %35 = load i32, i32* %34, align 8 135 | ret i32 %35 136 | } 137 | 138 | attributes #0 = { noinline nounwind optnone ssp uwtable "correctly-rounded-divide-sqrt-fp-math"="false" "disable-tail-calls"="false" "less-precise-fpmad"="false" "min-legal-vector-width"="0" "no-frame-pointer-elim"="true" "no-frame-pointer-elim-non-leaf" "no-infs-fp-math"="false" "no-jump-tables"="false" "no-nans-fp-math"="false" "no-signed-zeros-fp-math"="false" "no-trapping-math"="false" "stack-protector-buffer-size"="8" "target-cpu"="penryn" "target-features"="+cx16,+cx8,+fxsr,+mmx,+sahf,+sse,+sse2,+sse3,+sse4.1,+ssse3,+x87" "unsafe-fp-math"="false" "use-soft-float"="false" } 139 | 140 | !llvm.module.flags = !{!0, !1} 141 | !llvm.ident = !{!2} 142 | 143 | !0 = !{i32 1, !"wchar_size", i32 4} 144 | !1 = !{i32 7, !"PIC Level", i32 2} 145 | !2 = !{!"clang version 9.0.1 "} 146 | -------------------------------------------------------------------------------- /tests/call_tests.rs: -------------------------------------------------------------------------------- 1 | use haybale::*; 2 | use std::num::Wrapping; 3 | 4 | fn init_logging() { 5 | // capture log messages with test harness 6 | let _ = env_logger::builder().is_test(true).try_init(); 7 | } 8 | 9 | fn get_project() -> Project { 10 | let modname = "tests/bcfiles/call.bc"; 11 | Project::from_bc_path(modname) 12 | .unwrap_or_else(|e| panic!("Failed to parse module {:?}: {}", modname, e)) 13 | } 14 | 15 | #[test] 16 | fn simple_call() { 17 | let funcname = "simple_caller"; 18 | init_logging(); 19 | let proj = get_project(); 20 | let args = find_zero_of_func(funcname, &proj, Config::default(), None) 21 | .unwrap_or_else(|r| panic!("{}", r)) 22 | .expect("Failed to find zero of the function"); 23 | assert_eq!(args.len(), 1); 24 | assert_eq!(args[0], SolutionValue::I32(3)); 25 | } 26 | 27 | #[test] 28 | fn cross_module_simple_call() { 29 | let callee_modname = "tests/bcfiles/call.bc"; 30 | let caller_modname = "tests/bcfiles/crossmod.bc"; 31 | let funcname = "cross_module_simple_caller"; 32 | init_logging(); 33 | let proj = Project::from_bc_paths(&[callee_modname, caller_modname]) 34 | .unwrap_or_else(|e| panic!("Failed to parse modules: {}", e)); 35 | let args = find_zero_of_func(funcname, &proj, Config::default(), None) 36 | .unwrap_or_else(|r| panic!("{}", r)) 37 | .expect("Failed to find zero of the function"); 38 | assert_eq!(args.len(), 1); 39 | assert_eq!(args[0], SolutionValue::I32(3)); 40 | } 41 | 42 | #[test] 43 | fn conditional_call() { 44 | let funcname = "conditional_caller"; 45 | init_logging(); 46 | let proj = get_project(); 47 | let args = find_zero_of_func(funcname, &proj, Config::default(), None) 48 | .unwrap_or_else(|r| panic!("{}", r)) 49 | .expect("Failed to find zero of the function"); 50 | assert_eq!(args.len(), 2); 51 | assert_eq!(args[0], SolutionValue::I32(3)); 52 | assert!(args[1].unwrap_to_i32() > 5); 53 | } 54 | 55 | #[test] 56 | fn call_twice() { 57 | let funcname = "twice_caller"; 58 | init_logging(); 59 | let proj = get_project(); 60 | let args = find_zero_of_func(funcname, &proj, Config::default(), None) 61 | .unwrap_or_else(|r| panic!("{}", r)) 62 | .expect("Failed to find zero of the function"); 63 | assert_eq!(args.len(), 1); 64 | assert_eq!(args[0], SolutionValue::I32(3)); 65 | } 66 | 67 | #[test] 68 | fn cross_module_call_twice() { 69 | let callee_modname = "tests/bcfiles/call.bc"; 70 | let caller_modname = "tests/bcfiles/crossmod.bc"; 71 | let funcname = "cross_module_twice_caller"; 72 | init_logging(); 73 | let proj = Project::from_bc_paths(&[callee_modname, caller_modname]) 74 | .unwrap_or_else(|e| panic!("Failed to parse modules: {}", e)); 75 | let args = find_zero_of_func(funcname, &proj, Config::default(), None) 76 | .unwrap_or_else(|r| panic!("{}", r)) 77 | .expect("Failed to find zero of the function"); 78 | assert_eq!(args.len(), 1); 79 | assert_eq!(args[0], SolutionValue::I32(3)); 80 | } 81 | 82 | #[test] 83 | fn nested_call() { 84 | let funcname = "nested_caller"; 85 | init_logging(); 86 | let proj = get_project(); 87 | let args = find_zero_of_func(funcname, &proj, Config::default(), None) 88 | .unwrap_or_else(|r| panic!("{}", r)) 89 | .expect("Failed to find zero of the function"); 90 | assert_eq!(args.len(), 2); 91 | let x = Wrapping(args[0].unwrap_to_i32()); 92 | let y = Wrapping(args[1].unwrap_to_i32()); 93 | println!("x = {}, y = {}", x, y); 94 | assert_eq!((x + y).0, 3); 95 | } 96 | 97 | #[test] 98 | fn cross_module_nested_near_call() { 99 | let callee_modname = "tests/bcfiles/call.bc"; 100 | let caller_modname = "tests/bcfiles/crossmod.bc"; 101 | let funcname = "cross_module_nested_near_caller"; 102 | init_logging(); 103 | let proj = Project::from_bc_paths(&[callee_modname, caller_modname]) 104 | .unwrap_or_else(|e| panic!("Failed to parse modules: {}", e)); 105 | let args = find_zero_of_func(funcname, &proj, Config::default(), None) 106 | .unwrap_or_else(|r| panic!("{}", r)) 107 | .expect("Failed to find zero of the function"); 108 | assert_eq!(args.len(), 2); 109 | let x = Wrapping(args[0].unwrap_to_i32()); 110 | let y = Wrapping(args[1].unwrap_to_i32()); 111 | println!("x = {}, y = {}", x, y); 112 | assert_eq!((x + y).0, 3); 113 | } 114 | 115 | #[test] 116 | fn cross_module_nested_far_call() { 117 | let callee_modname = "tests/bcfiles/call.bc"; 118 | let caller_modname = "tests/bcfiles/crossmod.bc"; 119 | let funcname = "cross_module_nested_far_caller"; 120 | init_logging(); 121 | let proj = Project::from_bc_paths(&[callee_modname, caller_modname]) 122 | .unwrap_or_else(|e| panic!("Failed to parse modules: {}", e)); 123 | let args = find_zero_of_func(funcname, &proj, Config::default(), None) 124 | .unwrap_or_else(|r| panic!("{}", r)) 125 | .expect("Failed to find zero of the function"); 126 | assert_eq!(args.len(), 2); 127 | let x = Wrapping(args[0].unwrap_to_i32()); 128 | let y = Wrapping(args[1].unwrap_to_i32()); 129 | println!("x = {}, y = {}", x, y); 130 | assert_eq!((x + y).0, 3); 131 | } 132 | 133 | #[test] 134 | fn call_of_loop() { 135 | let funcname = "caller_of_loop"; 136 | init_logging(); 137 | let proj = get_project(); 138 | let args = find_zero_of_func(funcname, &proj, Config::default(), None) 139 | .unwrap_or_else(|r| panic!("{}", r)) 140 | .expect("Failed to find zero of the function"); 141 | assert_eq!(args.len(), 1); 142 | assert_eq!(args[0], SolutionValue::I32(3)); 143 | } 144 | 145 | #[test] 146 | fn call_in_loop() { 147 | let funcname = "caller_with_loop"; 148 | init_logging(); 149 | let proj = get_project(); 150 | let args = find_zero_of_func(funcname, &proj, Config::default(), None) 151 | .unwrap_or_else(|r| panic!("{}", r)) 152 | .expect("Failed to find zero of the function"); 153 | assert_eq!(args.len(), 1); 154 | assert_eq!(args[0], SolutionValue::I32(3)); 155 | } 156 | 157 | #[test] 158 | fn recursive_simple() { 159 | let funcname = "recursive_simple"; 160 | init_logging(); 161 | let proj = get_project(); 162 | let args = find_zero_of_func(funcname, &proj, Config::default(), None) 163 | .unwrap_or_else(|r| panic!("{}", r)) 164 | .expect("Failed to find zero of the function"); 165 | assert_eq!(args.len(), 1); 166 | let x = Wrapping(args[0].unwrap_to_i32()); 167 | println!("x = {}", x.0); 168 | assert_eq!(recursive_simple_dummy(x).0, 0); 169 | assert_eq!(args[0], SolutionValue::I32(11)); 170 | } 171 | 172 | fn recursive_simple_dummy(x: Wrapping) -> Wrapping { 173 | let y = x * Wrapping(2); 174 | if y > Wrapping(25) { 175 | y 176 | } else { 177 | recursive_simple_dummy(y) - Wrapping(44) 178 | } 179 | } 180 | 181 | #[test] 182 | fn recursive_double() { 183 | let funcname = "recursive_double"; 184 | init_logging(); 185 | let proj = get_project(); 186 | let args = find_zero_of_func(funcname, &proj, Config::default(), None) 187 | .unwrap_or_else(|r| panic!("{}", r)) 188 | .expect("Failed to find zero of the function"); 189 | assert_eq!(args.len(), 1); 190 | assert_eq!(args[0], SolutionValue::I32(-6)); 191 | } 192 | 193 | #[test] 194 | fn recursive_not_tail() { 195 | let funcname = "recursive_not_tail"; 196 | init_logging(); 197 | let proj = get_project(); 198 | let args = find_zero_of_func(funcname, &proj, Config::default(), None) 199 | .unwrap_or_else(|r| panic!("{}", r)) 200 | .expect("Failed to find zero of the function"); 201 | assert_eq!(args.len(), 1); 202 | let x = Wrapping(args[0].unwrap_to_i32()); 203 | println!("x = {}", x.0); 204 | assert_eq!(recursive_not_tail_dummy(x).0, 0); 205 | } 206 | 207 | fn recursive_not_tail_dummy(x: Wrapping) -> Wrapping { 208 | if x > Wrapping(100) { 209 | x + Wrapping(10) 210 | } else { 211 | let a = recursive_not_tail_dummy(x + Wrapping(20)); 212 | if a % Wrapping(2) == Wrapping(0) { 213 | a % Wrapping(3) 214 | } else { 215 | (a % Wrapping(5)) - Wrapping(8) 216 | } 217 | } 218 | } 219 | 220 | #[test] 221 | fn recursive_and_normal_call() { 222 | let funcname = "recursive_and_normal_caller"; 223 | init_logging(); 224 | let proj = get_project(); 225 | let args = find_zero_of_func(funcname, &proj, Config::default(), None) 226 | .unwrap_or_else(|r| panic!("{}", r)) 227 | .expect("Failed to find zero of the function"); 228 | assert_eq!(args.len(), 1); 229 | assert_eq!(args[0], SolutionValue::I32(11)); 230 | } 231 | 232 | #[test] 233 | fn mutually_recursive_functions() { 234 | let funcname = "mutually_recursive_a"; 235 | init_logging(); 236 | let proj = get_project(); 237 | let args = find_zero_of_func(funcname, &proj, Config::default(), None) 238 | .unwrap_or_else(|r| panic!("{}", r)) 239 | .expect("Failed to find zero of the function"); 240 | assert_eq!(args.len(), 1); 241 | //assert_eq!(args[0], SolutionValue::I32(3)) 242 | } 243 | 244 | #[test] 245 | fn test_pretty_path_llvm_instructions() { 246 | let funcname = "nested_caller"; 247 | init_logging(); 248 | let proj = get_project(); 249 | let mut em: ExecutionManager = 250 | symex_function(funcname, &proj, Config::default(), None).unwrap(); 251 | em.next().expect("Expected a path").unwrap(); 252 | let state = em.state(); 253 | let len = state.get_path_length(); 254 | let instrs = state.pretty_path_llvm_instructions(); 255 | let actual_instrs = "%3 = add i32 %1, i32 %0\n\ 256 | %4 = tail call @simple_caller(i32 %3)\n\ 257 | %2 = tail call @simple_callee(i32 %0, i32 3)\n\ 258 | %3 = sub i32 %0, i32 %1\n\ 259 | ret i32 %3\n\ 260 | ret i32 %2\n\ 261 | ret i32 %4\n"; 262 | 263 | assert_eq!(len, instrs.matches("\n").count()); 264 | assert_eq!(instrs, actual_instrs,); 265 | assert!(em.next().is_none(), "Expected only one path"); 266 | } 267 | -------------------------------------------------------------------------------- /tests/bcfiles/issue_4.ll: -------------------------------------------------------------------------------- 1 | ; ModuleID = 'issue_4.3a1fbbbh-cgu.0' 2 | source_filename = "issue_4.3a1fbbbh-cgu.0" 3 | target datalayout = "e-m:o-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128" 4 | target triple = "x86_64-apple-macosx10.7.0" 5 | 6 | %"core::fmt::Formatter" = type { [0 x i64], { i64, i64 }, [0 x i64], { i64, i64 }, [0 x i64], { {}*, [3 x i64]* }, [0 x i32], i32, [0 x i32], i32, [0 x i8], i8, [7 x i8] } 7 | %"core::fmt::::Opaque" = type {} 8 | %"core::fmt::Arguments" = type { [0 x i64], { [0 x { [0 x i8]*, i64 }]*, i64 }, [0 x i64], { i64*, i64 }, [0 x i64], { [0 x { i8*, i8* }]*, i64 }, [0 x i64] } 9 | %"core::panic::Location" = type { [0 x i64], { [0 x i8]*, i64 }, [0 x i32], i32, [0 x i32], i32, [0 x i32] } 10 | 11 | @alloc8 = private unnamed_addr constant <{ [10 x i8] }> <{ [10 x i8] c"issue_4.rs" }>, align 1 12 | @alloc9 = private unnamed_addr constant <{ i8*, [16 x i8] }> <{ i8* getelementptr inbounds (<{ [10 x i8] }>, <{ [10 x i8] }>* @alloc8, i32 0, i32 0, i32 0), [16 x i8] c"\0A\00\00\00\00\00\00\00\04\00\00\00\05\00\00\00" }>, align 8 13 | @str.0 = internal constant [33 x i8] c"attempt to multiply with overflow" 14 | @alloc2 = private unnamed_addr constant <{ [5 x i8] }> <{ [5 x i8] c"out: " }>, align 1 15 | @alloc3 = private unnamed_addr constant <{ [1 x i8] }> <{ [1 x i8] c"\0A" }>, align 1 16 | @alloc4 = private unnamed_addr constant <{ i8*, [8 x i8], i8*, [8 x i8] }> <{ i8* getelementptr inbounds (<{ [5 x i8] }>, <{ [5 x i8] }>* @alloc2, i32 0, i32 0, i32 0), [8 x i8] c"\05\00\00\00\00\00\00\00", i8* getelementptr inbounds (<{ [1 x i8] }>, <{ [1 x i8] }>* @alloc3, i32 0, i32 0, i32 0), [8 x i8] c"\01\00\00\00\00\00\00\00" }>, align 8 17 | @0 = private unnamed_addr constant <{ i8*, [0 x i8] }> <{ i8* bitcast (<{ i8*, [8 x i8], i8*, [8 x i8] }>* @alloc4 to i8*), [0 x i8] zeroinitializer }>, align 8 18 | 19 | ; core::fmt::ArgumentV1::new 20 | ; Function Attrs: uwtable 21 | define { i8*, i8* } @_ZN4core3fmt10ArgumentV13new17h1255362ef8e19b1aE(i32* noalias readonly align 4 dereferenceable(4) %x, i1 (i32*, %"core::fmt::Formatter"*)* nonnull %f) unnamed_addr #0 { 22 | start: 23 | %0 = alloca %"core::fmt::::Opaque"*, align 8 24 | %1 = alloca i1 (%"core::fmt::::Opaque"*, %"core::fmt::Formatter"*)*, align 8 25 | %2 = alloca { i8*, i8* }, align 8 26 | %3 = bitcast i1 (%"core::fmt::::Opaque"*, %"core::fmt::Formatter"*)** %1 to i1 (i32*, %"core::fmt::Formatter"*)** 27 | store i1 (i32*, %"core::fmt::Formatter"*)* %f, i1 (i32*, %"core::fmt::Formatter"*)** %3, align 8 28 | %_3 = load i1 (%"core::fmt::::Opaque"*, %"core::fmt::Formatter"*)*, i1 (%"core::fmt::::Opaque"*, %"core::fmt::Formatter"*)** %1, align 8, !nonnull !1 29 | br label %bb1 30 | 31 | bb1: ; preds = %start 32 | %4 = bitcast %"core::fmt::::Opaque"** %0 to i32** 33 | store i32* %x, i32** %4, align 8 34 | %_5 = load %"core::fmt::::Opaque"*, %"core::fmt::::Opaque"** %0, align 8, !nonnull !1 35 | br label %bb2 36 | 37 | bb2: ; preds = %bb1 38 | %5 = bitcast { i8*, i8* }* %2 to %"core::fmt::::Opaque"** 39 | store %"core::fmt::::Opaque"* %_5, %"core::fmt::::Opaque"** %5, align 8 40 | %6 = getelementptr inbounds { i8*, i8* }, { i8*, i8* }* %2, i32 0, i32 1 41 | %7 = bitcast i8** %6 to i1 (%"core::fmt::::Opaque"*, %"core::fmt::Formatter"*)** 42 | store i1 (%"core::fmt::::Opaque"*, %"core::fmt::Formatter"*)* %_3, i1 (%"core::fmt::::Opaque"*, %"core::fmt::Formatter"*)** %7, align 8 43 | %8 = getelementptr inbounds { i8*, i8* }, { i8*, i8* }* %2, i32 0, i32 0 44 | %9 = load i8*, i8** %8, align 8, !nonnull !1 45 | %10 = getelementptr inbounds { i8*, i8* }, { i8*, i8* }* %2, i32 0, i32 1 46 | %11 = load i8*, i8** %10, align 8, !nonnull !1 47 | %12 = insertvalue { i8*, i8* } undef, i8* %9, 0 48 | %13 = insertvalue { i8*, i8* } %12, i8* %11, 1 49 | ret { i8*, i8* } %13 50 | } 51 | 52 | ; core::fmt::Arguments::new_v1 53 | ; Function Attrs: inlinehint uwtable 54 | define internal void @_ZN4core3fmt9Arguments6new_v117hcb411028d393ec8fE(%"core::fmt::Arguments"* noalias nocapture sret dereferenceable(48) %0, [0 x { [0 x i8]*, i64 }]* noalias nonnull readonly align 8 %pieces.0, i64 %pieces.1, [0 x { i8*, i8* }]* noalias nonnull readonly align 8 %args.0, i64 %args.1) unnamed_addr #1 { 55 | start: 56 | %_4 = alloca { i64*, i64 }, align 8 57 | %1 = bitcast { i64*, i64 }* %_4 to {}** 58 | store {}* null, {}** %1, align 8 59 | %2 = bitcast %"core::fmt::Arguments"* %0 to { [0 x { [0 x i8]*, i64 }]*, i64 }* 60 | %3 = getelementptr inbounds { [0 x { [0 x i8]*, i64 }]*, i64 }, { [0 x { [0 x i8]*, i64 }]*, i64 }* %2, i32 0, i32 0 61 | store [0 x { [0 x i8]*, i64 }]* %pieces.0, [0 x { [0 x i8]*, i64 }]** %3, align 8 62 | %4 = getelementptr inbounds { [0 x { [0 x i8]*, i64 }]*, i64 }, { [0 x { [0 x i8]*, i64 }]*, i64 }* %2, i32 0, i32 1 63 | store i64 %pieces.1, i64* %4, align 8 64 | %5 = getelementptr inbounds %"core::fmt::Arguments", %"core::fmt::Arguments"* %0, i32 0, i32 3 65 | %6 = getelementptr inbounds { i64*, i64 }, { i64*, i64 }* %_4, i32 0, i32 0 66 | %7 = load i64*, i64** %6, align 8 67 | %8 = getelementptr inbounds { i64*, i64 }, { i64*, i64 }* %_4, i32 0, i32 1 68 | %9 = load i64, i64* %8, align 8 69 | %10 = getelementptr inbounds { i64*, i64 }, { i64*, i64 }* %5, i32 0, i32 0 70 | store i64* %7, i64** %10, align 8 71 | %11 = getelementptr inbounds { i64*, i64 }, { i64*, i64 }* %5, i32 0, i32 1 72 | store i64 %9, i64* %11, align 8 73 | %12 = getelementptr inbounds %"core::fmt::Arguments", %"core::fmt::Arguments"* %0, i32 0, i32 5 74 | %13 = getelementptr inbounds { [0 x { i8*, i8* }]*, i64 }, { [0 x { i8*, i8* }]*, i64 }* %12, i32 0, i32 0 75 | store [0 x { i8*, i8* }]* %args.0, [0 x { i8*, i8* }]** %13, align 8 76 | %14 = getelementptr inbounds { [0 x { i8*, i8* }]*, i64 }, { [0 x { i8*, i8* }]*, i64 }* %12, i32 0, i32 1 77 | store i64 %args.1, i64* %14, align 8 78 | ret void 79 | } 80 | 81 | ; issue_4::ez 82 | ; Function Attrs: uwtable 83 | define i32 @_ZN7issue_42ez17h3ecae08f8205d50dE(i32 %input) unnamed_addr #0 { 84 | start: 85 | %0 = call { i32, i1 } @llvm.umul.with.overflow.i32(i32 %input, i32 2) 86 | %_3.0 = extractvalue { i32, i1 } %0, 0 87 | %_3.1 = extractvalue { i32, i1 } %0, 1 88 | %1 = call i1 @llvm.expect.i1(i1 %_3.1, i1 false) 89 | br i1 %1, label %panic, label %bb1 90 | 91 | bb1: ; preds = %start 92 | ret i32 %_3.0 93 | 94 | panic: ; preds = %start 95 | ; call core::panicking::panic 96 | call void @_ZN4core9panicking5panic17h2f2a3cb96f8c3d83E([0 x i8]* noalias nonnull readonly align 1 bitcast ([33 x i8]* @str.0 to [0 x i8]*), i64 33, %"core::panic::Location"* noalias readonly align 8 dereferenceable(24) bitcast (<{ i8*, [16 x i8] }>* @alloc9 to %"core::panic::Location"*)) 97 | unreachable 98 | } 99 | 100 | ; issue_4::main 101 | ; Function Attrs: uwtable 102 | define void @_ZN7issue_44main17hce56232ad4472924E() unnamed_addr #0 { 103 | start: 104 | %_11 = alloca i32*, align 8 105 | %_10 = alloca [1 x { i8*, i8* }], align 8 106 | %_3 = alloca %"core::fmt::Arguments", align 8 107 | %out = alloca i32, align 4 108 | ; call issue_4::ez 109 | %0 = call i32 @_ZN7issue_42ez17h3ecae08f8205d50dE(i32 1) 110 | store i32 %0, i32* %out, align 4 111 | br label %bb1 112 | 113 | bb1: ; preds = %start 114 | %_17 = load [2 x { [0 x i8]*, i64 }]*, [2 x { [0 x i8]*, i64 }]** bitcast (<{ i8*, [0 x i8] }>* @0 to [2 x { [0 x i8]*, i64 }]**), align 8, !nonnull !1 115 | %_4.0 = bitcast [2 x { [0 x i8]*, i64 }]* %_17 to [0 x { [0 x i8]*, i64 }]* 116 | store i32* %out, i32** %_11, align 8 117 | %arg0 = load i32*, i32** %_11, align 8, !nonnull !1 118 | ; call core::fmt::ArgumentV1::new 119 | %1 = call { i8*, i8* } @_ZN4core3fmt10ArgumentV13new17h1255362ef8e19b1aE(i32* noalias readonly align 4 dereferenceable(4) %arg0, i1 (i32*, %"core::fmt::Formatter"*)* nonnull @"_ZN4core3fmt3num3imp52_$LT$impl$u20$core..fmt..Display$u20$for$u20$u32$GT$3fmt17h9718cd0454163961E") 120 | %_14.0 = extractvalue { i8*, i8* } %1, 0 121 | %_14.1 = extractvalue { i8*, i8* } %1, 1 122 | br label %bb2 123 | 124 | bb2: ; preds = %bb1 125 | %2 = bitcast [1 x { i8*, i8* }]* %_10 to { i8*, i8* }* 126 | %3 = getelementptr inbounds { i8*, i8* }, { i8*, i8* }* %2, i32 0, i32 0 127 | store i8* %_14.0, i8** %3, align 8 128 | %4 = getelementptr inbounds { i8*, i8* }, { i8*, i8* }* %2, i32 0, i32 1 129 | store i8* %_14.1, i8** %4, align 8 130 | %_7.0 = bitcast [1 x { i8*, i8* }]* %_10 to [0 x { i8*, i8* }]* 131 | ; call core::fmt::Arguments::new_v1 132 | call void @_ZN4core3fmt9Arguments6new_v117hcb411028d393ec8fE(%"core::fmt::Arguments"* noalias nocapture sret dereferenceable(48) %_3, [0 x { [0 x i8]*, i64 }]* noalias nonnull readonly align 8 %_4.0, i64 2, [0 x { i8*, i8* }]* noalias nonnull readonly align 8 %_7.0, i64 1) 133 | br label %bb3 134 | 135 | bb3: ; preds = %bb2 136 | ; call std::io::stdio::_print 137 | call void @_ZN3std2io5stdio6_print17he30fba25a77fc277E(%"core::fmt::Arguments"* noalias nocapture dereferenceable(48) %_3) 138 | br label %bb4 139 | 140 | bb4: ; preds = %bb3 141 | ret void 142 | } 143 | 144 | ; Function Attrs: nounwind readnone speculatable willreturn 145 | declare { i32, i1 } @llvm.umul.with.overflow.i32(i32, i32) #2 146 | 147 | ; Function Attrs: nounwind readnone willreturn 148 | declare i1 @llvm.expect.i1(i1, i1) #3 149 | 150 | ; core::panicking::panic 151 | ; Function Attrs: cold noinline noreturn uwtable 152 | declare void @_ZN4core9panicking5panic17h2f2a3cb96f8c3d83E([0 x i8]* noalias nonnull readonly align 1, i64, %"core::panic::Location"* noalias readonly align 8 dereferenceable(24)) unnamed_addr #4 153 | 154 | ; core::fmt::num::imp::::fmt 155 | ; Function Attrs: uwtable 156 | declare zeroext i1 @"_ZN4core3fmt3num3imp52_$LT$impl$u20$core..fmt..Display$u20$for$u20$u32$GT$3fmt17h9718cd0454163961E"(i32* noalias readonly align 4 dereferenceable(4), %"core::fmt::Formatter"* align 8 dereferenceable(64)) unnamed_addr #0 157 | 158 | ; std::io::stdio::_print 159 | ; Function Attrs: uwtable 160 | declare void @_ZN3std2io5stdio6_print17he30fba25a77fc277E(%"core::fmt::Arguments"* noalias nocapture dereferenceable(48)) unnamed_addr #0 161 | 162 | attributes #0 = { uwtable "frame-pointer"="all" "probe-stack"="__rust_probestack" "target-cpu"="core2" } 163 | attributes #1 = { inlinehint uwtable "frame-pointer"="all" "probe-stack"="__rust_probestack" "target-cpu"="core2" } 164 | attributes #2 = { nounwind readnone speculatable willreturn } 165 | attributes #3 = { nounwind readnone willreturn } 166 | attributes #4 = { cold noinline noreturn uwtable "frame-pointer"="all" "probe-stack"="__rust_probestack" "target-cpu"="core2" } 167 | 168 | !llvm.module.flags = !{!0} 169 | 170 | !0 = !{i32 7, !"PIC Level", i32 2} 171 | !1 = !{} 172 | -------------------------------------------------------------------------------- /tests/bcfiles/32bit/issue_4.ll: -------------------------------------------------------------------------------- 1 | ; ModuleID = 'issue_4.3a1fbbbh-cgu.0' 2 | source_filename = "issue_4.3a1fbbbh-cgu.0" 3 | target datalayout = "e-m:e-p:32:32-p270:32:32-p271:32:32-p272:64:64-f64:32:64-f80:32-n8:16:32-S128" 4 | target triple = "i686-unknown-linux-gnu" 5 | 6 | %"core::fmt::Formatter" = type { [0 x i32], i32, [0 x i32], i32, [0 x i32], { i32, i32 }, [0 x i32], { i32, i32 }, [0 x i32], { {}*, [3 x i32]* }, [0 x i8], i8, [3 x i8] } 7 | %"core::fmt::::Opaque" = type {} 8 | %"core::fmt::Arguments" = type { [0 x i32], { [0 x { [0 x i8]*, i32 }]*, i32 }, [0 x i32], { i32*, i32 }, [0 x i32], { [0 x { i8*, i8* }]*, i32 }, [0 x i32] } 9 | %"core::panic::Location" = type { [0 x i32], { [0 x i8]*, i32 }, [0 x i32], i32, [0 x i32], i32, [0 x i32] } 10 | 11 | @alloc8 = private unnamed_addr constant <{ [10 x i8] }> <{ [10 x i8] c"issue_4.rs" }>, align 1 12 | @alloc9 = private unnamed_addr constant <{ i8*, [12 x i8] }> <{ i8* getelementptr inbounds (<{ [10 x i8] }>, <{ [10 x i8] }>* @alloc8, i32 0, i32 0, i32 0), [12 x i8] c"\0A\00\00\00\04\00\00\00\05\00\00\00" }>, align 4 13 | @str.0 = internal constant [33 x i8] c"attempt to multiply with overflow" 14 | @alloc2 = private unnamed_addr constant <{ [5 x i8] }> <{ [5 x i8] c"out: " }>, align 1 15 | @alloc3 = private unnamed_addr constant <{ [1 x i8] }> <{ [1 x i8] c"\0A" }>, align 1 16 | @alloc4 = private unnamed_addr constant <{ i8*, [4 x i8], i8*, [4 x i8] }> <{ i8* getelementptr inbounds (<{ [5 x i8] }>, <{ [5 x i8] }>* @alloc2, i32 0, i32 0, i32 0), [4 x i8] c"\05\00\00\00", i8* getelementptr inbounds (<{ [1 x i8] }>, <{ [1 x i8] }>* @alloc3, i32 0, i32 0, i32 0), [4 x i8] c"\01\00\00\00" }>, align 4 17 | @0 = private unnamed_addr constant <{ i8*, [0 x i8] }> <{ i8* bitcast (<{ i8*, [4 x i8], i8*, [4 x i8] }>* @alloc4 to i8*), [0 x i8] zeroinitializer }>, align 4 18 | 19 | ; core::fmt::ArgumentV1::new 20 | ; Function Attrs: nonlazybind uwtable 21 | define { i8*, i8* } @_ZN4core3fmt10ArgumentV13new17h9b213be7c32b2319E(i32* noalias readonly align 4 dereferenceable(4) %x, i1 (i32*, %"core::fmt::Formatter"*)* nonnull %f) unnamed_addr #0 { 22 | start: 23 | %0 = alloca %"core::fmt::::Opaque"*, align 4 24 | %1 = alloca i1 (%"core::fmt::::Opaque"*, %"core::fmt::Formatter"*)*, align 4 25 | %2 = alloca { i8*, i8* }, align 4 26 | %3 = bitcast i1 (%"core::fmt::::Opaque"*, %"core::fmt::Formatter"*)** %1 to i1 (i32*, %"core::fmt::Formatter"*)** 27 | store i1 (i32*, %"core::fmt::Formatter"*)* %f, i1 (i32*, %"core::fmt::Formatter"*)** %3, align 4 28 | %_3 = load i1 (%"core::fmt::::Opaque"*, %"core::fmt::Formatter"*)*, i1 (%"core::fmt::::Opaque"*, %"core::fmt::Formatter"*)** %1, align 4, !nonnull !2 29 | br label %bb1 30 | 31 | bb1: ; preds = %start 32 | %4 = bitcast %"core::fmt::::Opaque"** %0 to i32** 33 | store i32* %x, i32** %4, align 4 34 | %_5 = load %"core::fmt::::Opaque"*, %"core::fmt::::Opaque"** %0, align 4, !nonnull !2 35 | br label %bb2 36 | 37 | bb2: ; preds = %bb1 38 | %5 = bitcast { i8*, i8* }* %2 to %"core::fmt::::Opaque"** 39 | store %"core::fmt::::Opaque"* %_5, %"core::fmt::::Opaque"** %5, align 4 40 | %6 = getelementptr inbounds { i8*, i8* }, { i8*, i8* }* %2, i32 0, i32 1 41 | %7 = bitcast i8** %6 to i1 (%"core::fmt::::Opaque"*, %"core::fmt::Formatter"*)** 42 | store i1 (%"core::fmt::::Opaque"*, %"core::fmt::Formatter"*)* %_3, i1 (%"core::fmt::::Opaque"*, %"core::fmt::Formatter"*)** %7, align 4 43 | %8 = getelementptr inbounds { i8*, i8* }, { i8*, i8* }* %2, i32 0, i32 0 44 | %9 = load i8*, i8** %8, align 4, !nonnull !2 45 | %10 = getelementptr inbounds { i8*, i8* }, { i8*, i8* }* %2, i32 0, i32 1 46 | %11 = load i8*, i8** %10, align 4, !nonnull !2 47 | %12 = insertvalue { i8*, i8* } undef, i8* %9, 0 48 | %13 = insertvalue { i8*, i8* } %12, i8* %11, 1 49 | ret { i8*, i8* } %13 50 | } 51 | 52 | ; core::fmt::Arguments::new_v1 53 | ; Function Attrs: inlinehint nonlazybind uwtable 54 | define internal void @_ZN4core3fmt9Arguments6new_v117hccb3e0acc6ca8c39E(%"core::fmt::Arguments"* noalias nocapture sret dereferenceable(24) %0, [0 x { [0 x i8]*, i32 }]* noalias nonnull readonly align 4 %pieces.0, i32 %pieces.1, [0 x { i8*, i8* }]* noalias nonnull readonly align 4 %args.0, i32 %args.1) unnamed_addr #1 { 55 | start: 56 | %_4 = alloca { i32*, i32 }, align 4 57 | %1 = bitcast { i32*, i32 }* %_4 to {}** 58 | store {}* null, {}** %1, align 4 59 | %2 = bitcast %"core::fmt::Arguments"* %0 to { [0 x { [0 x i8]*, i32 }]*, i32 }* 60 | %3 = getelementptr inbounds { [0 x { [0 x i8]*, i32 }]*, i32 }, { [0 x { [0 x i8]*, i32 }]*, i32 }* %2, i32 0, i32 0 61 | store [0 x { [0 x i8]*, i32 }]* %pieces.0, [0 x { [0 x i8]*, i32 }]** %3, align 4 62 | %4 = getelementptr inbounds { [0 x { [0 x i8]*, i32 }]*, i32 }, { [0 x { [0 x i8]*, i32 }]*, i32 }* %2, i32 0, i32 1 63 | store i32 %pieces.1, i32* %4, align 4 64 | %5 = getelementptr inbounds %"core::fmt::Arguments", %"core::fmt::Arguments"* %0, i32 0, i32 3 65 | %6 = getelementptr inbounds { i32*, i32 }, { i32*, i32 }* %_4, i32 0, i32 0 66 | %7 = load i32*, i32** %6, align 4 67 | %8 = getelementptr inbounds { i32*, i32 }, { i32*, i32 }* %_4, i32 0, i32 1 68 | %9 = load i32, i32* %8, align 4 69 | %10 = getelementptr inbounds { i32*, i32 }, { i32*, i32 }* %5, i32 0, i32 0 70 | store i32* %7, i32** %10, align 4 71 | %11 = getelementptr inbounds { i32*, i32 }, { i32*, i32 }* %5, i32 0, i32 1 72 | store i32 %9, i32* %11, align 4 73 | %12 = getelementptr inbounds %"core::fmt::Arguments", %"core::fmt::Arguments"* %0, i32 0, i32 5 74 | %13 = getelementptr inbounds { [0 x { i8*, i8* }]*, i32 }, { [0 x { i8*, i8* }]*, i32 }* %12, i32 0, i32 0 75 | store [0 x { i8*, i8* }]* %args.0, [0 x { i8*, i8* }]** %13, align 4 76 | %14 = getelementptr inbounds { [0 x { i8*, i8* }]*, i32 }, { [0 x { i8*, i8* }]*, i32 }* %12, i32 0, i32 1 77 | store i32 %args.1, i32* %14, align 4 78 | ret void 79 | } 80 | 81 | ; issue_4::ez 82 | ; Function Attrs: nonlazybind uwtable 83 | define i32 @_ZN7issue_42ez17h3ecae08f8205d50dE(i32 %input) unnamed_addr #0 { 84 | start: 85 | %0 = call { i32, i1 } @llvm.umul.with.overflow.i32(i32 %input, i32 2) 86 | %_3.0 = extractvalue { i32, i1 } %0, 0 87 | %_3.1 = extractvalue { i32, i1 } %0, 1 88 | %1 = call i1 @llvm.expect.i1(i1 %_3.1, i1 false) 89 | br i1 %1, label %panic, label %bb1 90 | 91 | bb1: ; preds = %start 92 | ret i32 %_3.0 93 | 94 | panic: ; preds = %start 95 | ; call core::panicking::panic 96 | call void @_ZN4core9panicking5panic17hdaab655da8250769E([0 x i8]* noalias nonnull readonly align 1 bitcast ([33 x i8]* @str.0 to [0 x i8]*), i32 33, %"core::panic::Location"* noalias readonly align 4 dereferenceable(16) bitcast (<{ i8*, [12 x i8] }>* @alloc9 to %"core::panic::Location"*)) 97 | unreachable 98 | } 99 | 100 | ; issue_4::main 101 | ; Function Attrs: nonlazybind uwtable 102 | define void @_ZN7issue_44main17hce56232ad4472924E() unnamed_addr #0 { 103 | start: 104 | %_11 = alloca i32*, align 4 105 | %_10 = alloca [1 x { i8*, i8* }], align 4 106 | %_3 = alloca %"core::fmt::Arguments", align 4 107 | %out = alloca i32, align 4 108 | ; call issue_4::ez 109 | %0 = call i32 @_ZN7issue_42ez17h3ecae08f8205d50dE(i32 1) 110 | store i32 %0, i32* %out, align 4 111 | br label %bb1 112 | 113 | bb1: ; preds = %start 114 | %_17 = load [2 x { [0 x i8]*, i32 }]*, [2 x { [0 x i8]*, i32 }]** bitcast (<{ i8*, [0 x i8] }>* @0 to [2 x { [0 x i8]*, i32 }]**), align 4, !nonnull !2 115 | %_4.0 = bitcast [2 x { [0 x i8]*, i32 }]* %_17 to [0 x { [0 x i8]*, i32 }]* 116 | store i32* %out, i32** %_11, align 4 117 | %arg0 = load i32*, i32** %_11, align 4, !nonnull !2 118 | ; call core::fmt::ArgumentV1::new 119 | %1 = call { i8*, i8* } @_ZN4core3fmt10ArgumentV13new17h9b213be7c32b2319E(i32* noalias readonly align 4 dereferenceable(4) %arg0, i1 (i32*, %"core::fmt::Formatter"*)* nonnull @"_ZN4core3fmt3num3imp52_$LT$impl$u20$core..fmt..Display$u20$for$u20$u32$GT$3fmt17h2720722ff93c563bE") 120 | %_14.0 = extractvalue { i8*, i8* } %1, 0 121 | %_14.1 = extractvalue { i8*, i8* } %1, 1 122 | br label %bb2 123 | 124 | bb2: ; preds = %bb1 125 | %2 = bitcast [1 x { i8*, i8* }]* %_10 to { i8*, i8* }* 126 | %3 = getelementptr inbounds { i8*, i8* }, { i8*, i8* }* %2, i32 0, i32 0 127 | store i8* %_14.0, i8** %3, align 4 128 | %4 = getelementptr inbounds { i8*, i8* }, { i8*, i8* }* %2, i32 0, i32 1 129 | store i8* %_14.1, i8** %4, align 4 130 | %_7.0 = bitcast [1 x { i8*, i8* }]* %_10 to [0 x { i8*, i8* }]* 131 | ; call core::fmt::Arguments::new_v1 132 | call void @_ZN4core3fmt9Arguments6new_v117hccb3e0acc6ca8c39E(%"core::fmt::Arguments"* noalias nocapture sret dereferenceable(24) %_3, [0 x { [0 x i8]*, i32 }]* noalias nonnull readonly align 4 %_4.0, i32 2, [0 x { i8*, i8* }]* noalias nonnull readonly align 4 %_7.0, i32 1) 133 | br label %bb3 134 | 135 | bb3: ; preds = %bb2 136 | ; call std::io::stdio::_print 137 | call void @_ZN3std2io5stdio6_print17h2812396d3cb60f3cE(%"core::fmt::Arguments"* noalias nocapture dereferenceable(24) %_3) 138 | br label %bb4 139 | 140 | bb4: ; preds = %bb3 141 | ret void 142 | } 143 | 144 | ; Function Attrs: nounwind readnone speculatable willreturn 145 | declare { i32, i1 } @llvm.umul.with.overflow.i32(i32, i32) #2 146 | 147 | ; Function Attrs: nounwind readnone willreturn 148 | declare i1 @llvm.expect.i1(i1, i1) #3 149 | 150 | ; core::panicking::panic 151 | ; Function Attrs: cold noinline noreturn nonlazybind uwtable 152 | declare void @_ZN4core9panicking5panic17hdaab655da8250769E([0 x i8]* noalias nonnull readonly align 1, i32, %"core::panic::Location"* noalias readonly align 4 dereferenceable(16)) unnamed_addr #4 153 | 154 | ; core::fmt::num::imp::::fmt 155 | ; Function Attrs: nonlazybind uwtable 156 | declare zeroext i1 @"_ZN4core3fmt3num3imp52_$LT$impl$u20$core..fmt..Display$u20$for$u20$u32$GT$3fmt17h2720722ff93c563bE"(i32* noalias readonly align 4 dereferenceable(4), %"core::fmt::Formatter"* align 4 dereferenceable(36)) unnamed_addr #0 157 | 158 | ; std::io::stdio::_print 159 | ; Function Attrs: nonlazybind uwtable 160 | declare void @_ZN3std2io5stdio6_print17h2812396d3cb60f3cE(%"core::fmt::Arguments"* noalias nocapture dereferenceable(24)) unnamed_addr #0 161 | 162 | attributes #0 = { nonlazybind uwtable "probe-stack"="__rust_probestack" "target-cpu"="pentium4" } 163 | attributes #1 = { inlinehint nonlazybind uwtable "probe-stack"="__rust_probestack" "target-cpu"="pentium4" } 164 | attributes #2 = { nounwind readnone speculatable willreturn } 165 | attributes #3 = { nounwind readnone willreturn } 166 | attributes #4 = { cold noinline noreturn nonlazybind uwtable "probe-stack"="__rust_probestack" "target-cpu"="pentium4" } 167 | 168 | !llvm.module.flags = !{!0, !1} 169 | 170 | !0 = !{i32 7, !"PIC Level", i32 2} 171 | !1 = !{i32 2, !"RtLibUseGOT", i32 1} 172 | !2 = !{} 173 | --------------------------------------------------------------------------------