mirror of
https://github.com/Noratrieb/cargo-minimize.git
synced 2026-01-14 08:25:01 +01:00
169 lines
4.8 KiB
Rust
169 lines
4.8 KiB
Rust
//! Handles the --verify-fn flag.
|
|
//! It takes in a Rust closure like `|str| true` that takes in a `&str` and returns a bool.
|
|
|
|
use std::{fmt::Debug, mem::ManuallyDrop, str::FromStr};
|
|
|
|
use anyhow::{Context, Result};
|
|
use libloading::Symbol;
|
|
|
|
#[repr(C)]
|
|
pub struct RawOutput {
|
|
out_ptr: *const u8,
|
|
out_len: usize,
|
|
out_has_status: bool,
|
|
out_status: i32,
|
|
}
|
|
|
|
type CheckerCFn = unsafe extern "C" fn(*const RawOutput) -> bool;
|
|
|
|
#[derive(Clone, Copy)]
|
|
pub struct RustFunction {
|
|
func: CheckerCFn,
|
|
}
|
|
|
|
impl FromStr for RustFunction {
|
|
type Err = anyhow::Error;
|
|
|
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
|
Self::compile(s).context("compiling and loading rust function")
|
|
}
|
|
}
|
|
|
|
fn wrap_func_body(func: &str) -> Result<String> {
|
|
let closure = syn::parse_str::<syn::ExprClosure>(func).context("invalid rust syntax")?;
|
|
|
|
let syn_file = syn::parse_quote! {
|
|
#[repr(C)]
|
|
pub struct __RawOutput {
|
|
out_ptr: *const u8,
|
|
out_len: usize,
|
|
out_has_status: bool,
|
|
out_status: i32,
|
|
}
|
|
|
|
impl __RawOutput {
|
|
unsafe fn as_output<'a>(&self) -> __Output<'a> {
|
|
let slice = unsafe { std::slice::from_raw_parts(self.out_ptr, self.out_len) };
|
|
let out = std::str::from_utf8(slice).unwrap();
|
|
let status = self.out_has_status.then_some(self.out_status);
|
|
__Output {
|
|
out,
|
|
status,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone, Copy)]
|
|
struct __Output<'a> {
|
|
out: &'a str,
|
|
status: Option<i32>,
|
|
}
|
|
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn cargo_minimize_ffi_function(raw: *const __RawOutput) -> bool {
|
|
match std::panic::catch_unwind(|| __cargo_minimize_inner(raw)) {
|
|
Ok(bool) => bool,
|
|
Err(_) => std::process::abort(),
|
|
}
|
|
}
|
|
|
|
#[allow(unused_parens)]
|
|
unsafe fn __cargo_minimize_inner(__raw: *const __RawOutput) -> bool {
|
|
let __output = __raw.read();
|
|
let __output = __output.as_output();
|
|
|
|
fn ascribe_type<'a, F: FnOnce(__Output<'a>) -> bool>(f: F, output: __Output<'a>) -> bool {
|
|
f(output)
|
|
}
|
|
|
|
ascribe_type((#closure), __output)
|
|
}
|
|
};
|
|
|
|
crate::formatting::format(syn_file)
|
|
}
|
|
|
|
impl RustFunction {
|
|
pub fn compile(body: &str) -> Result<Self> {
|
|
use anyhow::bail;
|
|
use std::process::Command;
|
|
|
|
let file = tempfile::tempdir()?;
|
|
|
|
let full_file = wrap_func_body(body)?;
|
|
|
|
let source_path = file.path().join("source.rs");
|
|
|
|
std::fs::write(&source_path, full_file).context("writing source")?;
|
|
|
|
let mut rustc = Command::new("rustc");
|
|
rustc.arg(source_path);
|
|
rustc.args(["--crate-type=cdylib", "--crate-name=helper", "--emit=link"]);
|
|
rustc.current_dir(file.path());
|
|
|
|
let output = rustc.output().context("running rustc")?;
|
|
if !output.status.success() {
|
|
let stderr = String::from_utf8(output.stderr)?;
|
|
bail!("Failed to compile code: {stderr}");
|
|
}
|
|
|
|
// SAFETY: We are loading a simple rust cdylib, which does not do weird things. But we cannot unload Rust dylibs, so we use MD below.
|
|
let dylib = unsafe {
|
|
libloading::Library::new(file.path().join(libloading::library_filename("helper")))
|
|
.context("loading helper shared library")?
|
|
};
|
|
let dylib = ManuallyDrop::new(dylib);
|
|
|
|
let func: Symbol<CheckerCFn> = unsafe {
|
|
dylib
|
|
.get(b"cargo_minimize_ffi_function\0")
|
|
.context("failed to find entrypoint symbol")?
|
|
};
|
|
|
|
Ok(Self { func: *func })
|
|
}
|
|
|
|
pub fn call(&self, output: &str, code: Option<i32>) -> bool {
|
|
let out_ptr = output.as_ptr();
|
|
let out_len = output.len();
|
|
let (out_has_status, out_status) = match code {
|
|
Some(status) => (true, status),
|
|
None => (false, 0),
|
|
};
|
|
|
|
let output = RawOutput {
|
|
out_ptr,
|
|
out_len,
|
|
out_has_status,
|
|
out_status,
|
|
};
|
|
|
|
unsafe { (self.func)(&output) }
|
|
}
|
|
}
|
|
|
|
impl Debug for RustFunction {
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
f.debug_struct("RustFunction").finish_non_exhaustive()
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::RustFunction;
|
|
|
|
#[test]
|
|
fn basic_contains_work() {
|
|
let code = r#"|output| output.out.contains("test")"#;
|
|
|
|
let function = RustFunction::compile(code).unwrap();
|
|
|
|
let output = "this is a test";
|
|
let not_output = "this is not a tst";
|
|
|
|
let code = None;
|
|
|
|
assert!(function.call(output, code));
|
|
assert!(!function.call(not_output, code));
|
|
}
|
|
}
|