// get_sign.rs
// Showcase how we manually can interface Rust to KLEE
//

#![no_std]
#![no_main]

fn get_sign(x: i32) -> i32 {
    if x == 0 {
        return 0;
    }
    if x < 0 {
        return -1;
    } else {
        return 1;
    }
}

#[no_mangle]
fn main() {
    let mut a: i32 = 0;
    klee_make_symbolic!(&mut a, "a");
    get_sign(a);
}

// KLEE bindings

#[panic_handler]
fn panic(_info: &core::panic::PanicInfo) -> ! {
    // abort symbol caught by LLVM-KLEE
    unsafe { ll::abort() }
}

#[inline(always)]
pub fn klee_make_symbolic<T>(t: &mut T, name: &'static cstr_core::CStr) {
    unsafe {
        crate::ll::klee_make_symbolic(
            t as *mut T as *mut core::ffi::c_void,
            core::mem::size_of::<T>(),
            name.as_ptr() as *const cstr_core::c_char,
        )
    }
}

#[macro_export]
macro_rules! klee_make_symbolic {
    (&mut $id:expr, $name:expr) => {
        klee_make_symbolic(&mut $id, unsafe {
            cstr_core::CStr::from_bytes_with_nul_unchecked(concat!($name, "\0").as_bytes())
        })
    };
}

mod ll {
    //! Low level bindings
    extern "C" {
        pub fn abort() -> !;
        pub fn klee_make_symbolic(
            ptr: *mut core::ffi::c_void,    // pointer to the data
            size: usize,                    // size (in bytes)
            name: *const cstr_core::c_char, // pointer to zero-terminated C string
        );
    }
}

// A) Compile a Rust program for KLEE analysis.
//
// In order for KLEE analysis we need to generate LLVM-IR.
//
// > cargo rustc -v --example get_sign -- -C linker=true -C lto --emit=llvm-ir
//
// Here `rustc` tells cargo that we will call the compiler with arguments.
//
// `-v` that we want verbose output.
//
// Options after `--` is passed directly to to the compiler (`rustc`).
//
// `-C linker=true`, binds the linker to the `true` binary (a program that just returns).
// This way we can prevent the compiler to link to a binary (ugly right!).
//
// `-C --emit=llvm-ir`, to emit LLVM-IR in `.ll` form.
//
// `cargo` places the generated artifacts under the `target` directory.
//
// > ls target/debug/examples/
// get_sign-85c57be6132dac1d.d  get_sign-85c57be6132dac1d.ll  get_sign.d
//
// If you look at the compilation, above you find that `cargo` adds
// `extra-filename=-85c57be6132dac1d` (the hash might be different but)
// matches the hash for the `.ll` file.
//
// The hash is based on the setting for this particular build.
// This way, `cargo` keeps track of the builds, so you can several builds
// of the same files with different settings without name collisions.
//
// Now we can run KLEE on the generate LLVM-IR.
// (KLEE can read both `.bc` and `.ll` files, `.ll` files are human readable.)
//
// > klee target/debug/examples/get_sign-85c57be6132dac1d.ll
// KLEE: output directory is "/home/pln/courses/d7020e/klee_tutorial/target/debug/examples/klee-out-0"
// KLEE: Using Z3 solver backend
//
// KLEE: done: total instructions = 92
// KLEE: done: completed paths = 3
// KLEE: done: generated tests = 3
//
// (You need to give the right hash for the `.ll` file.)
//
// What was the generated hash.
//
// [your answer here]
//
// B) Inspecting the test cases.
//
// Figure out to run `ktest-tool` on the generated test cases.
// (Hint, it is just a matter of paths.)
//
// [your answer here]
//
// C) Replaying your test cases.
//
// Now we need to re-compile the `get_sign.rs` with bindings to
// the `libkleeRuntest`.
//
// The first thing we need to do is to generate an object file `.o`.
//
// > cd target/debug/examples
// > llc -filetype=obj -relocation-model=pic get_sign-85c57be6132dac1d.ll
//
// `llc` is the LLVM compiler, `-filetype=obj` to generate an object file and
// `-relocation-model=pic` is to obtain Position Independent Code (https://en.wikipedia.org/wiki/Position-independent_code)
// (This is needed since we don't know where it is going to be placed/linked later.)
//
// Verify that you now have an object file:
//
// > ls *.o
//
// [your answer here]
//
// Now we need to link it with the `libkleeRuntest`.
//
// > clang get_sign-85c57be6132dac1d.o -l kleeRuntest -o a.out
//
// (See `get_sign.c` for linking options if you don't have KLEE libs in default location.)
//
// Now we can replay the test case. However, we have compiled the example for `no_std`
// (as we target embedded applications).
//
// > KTEST_FILE=klee-last/test000001.ktest gdb a.out
//
// (gdb) break get_sign
// Breakpoint 1 at 0x1165: file examples/get_sign.rs, line 9.
//
// Now run the code in the debugger. What path was triggered.
//
// [your answer here]
//
// Change to test000002, what path does it trigger.
//
// [your answer here]
//
// And finally change to test000003, what path was triggered.
//
// [your answer here]
//
// D) Remarks and conclusions.
//
// You have now successfully:
//
// - Generated LLVM-IR from Rust code
// - Run KLEE on Rust code to generate tests.
// - Linked with the 'kleeRuntest`.
// - Replayed code in `gdb'.
//
// It was a bit of a hassle right!
//
// In the next part of the lab we will:
//
// Introduce a Rust library for the LLVM bindings.
// Introduce a cargo sub-command for:
// - building and running KLEE
// - replaying test cases
//
// With this at hand, ergonomics using KLEE is improved.
// (Arguably better than the C/C++ integration.)
//
// So why did we not start directly with the `cargo klee`.
//
// Well this is course at advanced level, where you
// learn not only to use tools, but how the work.
//
// The library `klee-sys` basically extends on the
// primitive KLEE bindings in this file.
//
// The `cargo klee` command basically extends on the
// steps you have taken to compile and run the tools
// in this lab.
//
// If you grasp this lab, the rest is just coding.
// You may find and fix bugs in the `klee-sys`,
// improve and `cargo klee` etc.
//
// At that point you are `computer science master`!