2024-03-19 14:43:01 +01:00
|
|
|
use crate::value::{Value, ValueType};
|
|
|
|
|
use anyhow::Context as _;
|
|
|
|
|
use anyhow::{bail, Result};
|
|
|
|
|
use lazy_static::lazy_static;
|
|
|
|
|
use nix_c_raw as raw;
|
|
|
|
|
use nix_store::store::Store;
|
|
|
|
|
use nix_util::context::Context;
|
2024-04-23 13:23:42 +02:00
|
|
|
use nix_util::string_return::callback_get_vec_u8;
|
2024-03-19 14:43:01 +01:00
|
|
|
use std::ffi::CString;
|
|
|
|
|
use std::ptr::null_mut;
|
|
|
|
|
use std::ptr::NonNull;
|
|
|
|
|
|
|
|
|
|
lazy_static! {
|
|
|
|
|
static ref INIT: Result<()> = {
|
|
|
|
|
unsafe {
|
|
|
|
|
raw::GC_allow_register_threads();
|
|
|
|
|
}
|
|
|
|
|
let context: Context = Context::new();
|
|
|
|
|
unsafe {
|
2024-05-09 12:29:32 -04:00
|
|
|
raw::libexpr_init(context.ptr());
|
2024-03-19 14:43:01 +01:00
|
|
|
}
|
|
|
|
|
context.check_err()?;
|
|
|
|
|
Ok(())
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
pub fn init() -> Result<()> {
|
|
|
|
|
let x = INIT.as_ref();
|
|
|
|
|
match x {
|
|
|
|
|
Ok(_) => Ok(()),
|
|
|
|
|
Err(e) => {
|
|
|
|
|
// Couldn't just clone the error, so we have to print it here.
|
|
|
|
|
Err(anyhow::format_err!("nix_libstore_init error: {}", e))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub struct EvalState {
|
|
|
|
|
eval_state: NonNull<raw::EvalState>,
|
|
|
|
|
store: Store,
|
|
|
|
|
context: Context,
|
|
|
|
|
}
|
|
|
|
|
impl EvalState {
|
|
|
|
|
pub fn new(store: Store) -> Result<Self> {
|
|
|
|
|
let context = Context::new();
|
|
|
|
|
|
|
|
|
|
init()?;
|
|
|
|
|
|
|
|
|
|
let eval_state = unsafe {
|
2024-05-09 12:29:32 -04:00
|
|
|
raw::state_create(
|
2024-03-19 14:43:01 +01:00
|
|
|
context.ptr(),
|
|
|
|
|
/* searchPath */ null_mut(),
|
|
|
|
|
store.raw_ptr(),
|
|
|
|
|
)
|
|
|
|
|
};
|
2024-05-09 12:41:34 -04:00
|
|
|
context.check_err()?;
|
2024-03-19 14:43:01 +01:00
|
|
|
if eval_state.is_null() {
|
2024-05-09 12:41:34 -04:00
|
|
|
panic!("nix_state_create returned a null pointer without an error");
|
2024-03-19 14:43:01 +01:00
|
|
|
}
|
|
|
|
|
Ok(EvalState {
|
|
|
|
|
eval_state: NonNull::new(eval_state).unwrap(),
|
|
|
|
|
store,
|
|
|
|
|
context,
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
pub fn raw_ptr(&self) -> *mut raw::EvalState {
|
|
|
|
|
self.eval_state.as_ptr()
|
|
|
|
|
}
|
|
|
|
|
pub fn store(&self) -> &Store {
|
|
|
|
|
&self.store
|
|
|
|
|
}
|
2024-04-08 16:23:59 +02:00
|
|
|
pub fn eval_from_string(&self, expr: &str, path: &str) -> Result<Value> {
|
2024-03-19 14:43:01 +01:00
|
|
|
let expr_ptr =
|
|
|
|
|
CString::new(expr).with_context(|| "eval_from_string: expr contains null byte")?;
|
|
|
|
|
let path_ptr =
|
|
|
|
|
CString::new(path).with_context(|| "eval_from_string: path contains null byte")?;
|
|
|
|
|
let value = self.new_value_uninitialized();
|
|
|
|
|
unsafe {
|
|
|
|
|
let ctx_ptr = self.context.ptr();
|
2024-05-09 12:29:32 -04:00
|
|
|
raw::expr_eval_from_string(
|
2024-03-19 14:43:01 +01:00
|
|
|
ctx_ptr,
|
|
|
|
|
self.raw_ptr(),
|
|
|
|
|
expr_ptr.as_ptr(),
|
|
|
|
|
path_ptr.as_ptr(),
|
|
|
|
|
value.raw_ptr(),
|
|
|
|
|
);
|
|
|
|
|
};
|
|
|
|
|
self.context.check_err()?;
|
|
|
|
|
Ok(value)
|
|
|
|
|
}
|
|
|
|
|
/** Try turn any Value into a Value that isn't a Thunk. */
|
|
|
|
|
pub fn force(&self, v: &Value) -> Result<()> {
|
|
|
|
|
unsafe {
|
2024-05-09 12:29:32 -04:00
|
|
|
raw::value_force(self.context.ptr(), self.raw_ptr(), v.raw_ptr());
|
2024-03-19 14:43:01 +01:00
|
|
|
}
|
|
|
|
|
self.context.check_err()
|
|
|
|
|
}
|
|
|
|
|
pub fn value_is_thunk(&self, value: &Value) -> bool {
|
|
|
|
|
let r = unsafe {
|
2024-05-09 12:29:32 -04:00
|
|
|
raw::get_type(self.context.ptr(), value.raw_ptr()) == raw::ValueType_NIX_TYPE_THUNK
|
2024-03-19 14:43:01 +01:00
|
|
|
};
|
|
|
|
|
self.context.check_err().unwrap();
|
|
|
|
|
r
|
|
|
|
|
}
|
|
|
|
|
pub fn value_type(&self, value: &Value) -> Result<ValueType> {
|
|
|
|
|
if self.value_is_thunk(value) {
|
|
|
|
|
self.force(value)?;
|
|
|
|
|
}
|
2024-05-09 12:29:32 -04:00
|
|
|
let r = unsafe { raw::get_type(self.context.ptr(), value.raw_ptr()) };
|
2024-03-19 14:43:01 +01:00
|
|
|
Ok(ValueType::from_raw(r))
|
|
|
|
|
}
|
2024-04-04 16:38:12 +02:00
|
|
|
/// Not exposed, because the caller must always explicitly handle the context or not accept one at all.
|
|
|
|
|
fn get_string(&self, value: &Value) -> Result<String> {
|
2024-04-23 13:23:42 +02:00
|
|
|
let mut raw_buffer: Vec<u8> = Vec::new();
|
|
|
|
|
unsafe {
|
2024-05-09 12:29:32 -04:00
|
|
|
raw::get_string(
|
2024-04-23 13:23:42 +02:00
|
|
|
self.context.ptr(),
|
|
|
|
|
value.raw_ptr(),
|
|
|
|
|
Some(callback_get_vec_u8),
|
|
|
|
|
&mut raw_buffer as *mut Vec<u8> as *mut std::ffi::c_void,
|
|
|
|
|
)
|
|
|
|
|
};
|
2024-04-04 16:38:12 +02:00
|
|
|
self.context.check_err()?;
|
2024-04-23 13:23:42 +02:00
|
|
|
String::from_utf8(raw_buffer)
|
|
|
|
|
.map_err(|e| anyhow::format_err!("Nix string is not valid UTF-8: {}", e))
|
2024-04-04 16:38:12 +02:00
|
|
|
}
|
|
|
|
|
/// NOTE: this will be replaced by two methods, one that also returns the context, and one that checks that the context is empty
|
|
|
|
|
pub fn require_string(&self, value: &Value) -> Result<String> {
|
|
|
|
|
let t = self.value_type(value)?;
|
|
|
|
|
if t != ValueType::String {
|
|
|
|
|
bail!("expected a string, but got a {:?}", t);
|
|
|
|
|
}
|
|
|
|
|
self.get_string(value)
|
|
|
|
|
}
|
2024-03-19 14:43:01 +01:00
|
|
|
|
|
|
|
|
fn new_value_uninitialized(&self) -> Value {
|
2024-05-09 12:29:32 -04:00
|
|
|
let value = unsafe { raw::alloc_value(self.context.ptr(), self.raw_ptr()) };
|
2024-03-19 14:43:01 +01:00
|
|
|
Value::new(value)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn gc_now() {
|
|
|
|
|
unsafe {
|
2024-05-09 12:29:32 -04:00
|
|
|
raw::gc_now();
|
2024-03-19 14:43:01 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** Run a function while making sure that the current thread is registered with the GC. */
|
|
|
|
|
pub fn gc_registering_current_thread<F, R>(f: F) -> Result<R>
|
|
|
|
|
where
|
|
|
|
|
F: FnOnce() -> R,
|
|
|
|
|
{
|
|
|
|
|
init()?;
|
|
|
|
|
if unsafe { raw::GC_thread_is_registered() } != 0 {
|
|
|
|
|
return Ok(f());
|
|
|
|
|
} else {
|
2024-05-09 12:41:34 -04:00
|
|
|
gc_register_my_thread()?;
|
2024-03-19 14:43:01 +01:00
|
|
|
let r = f();
|
|
|
|
|
unsafe {
|
|
|
|
|
raw::GC_unregister_my_thread();
|
|
|
|
|
}
|
|
|
|
|
return Ok(r);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn gc_register_my_thread() -> Result<()> {
|
|
|
|
|
unsafe {
|
|
|
|
|
let already_done = raw::GC_thread_is_registered();
|
|
|
|
|
if already_done != 0 {
|
|
|
|
|
return Ok(());
|
|
|
|
|
}
|
|
|
|
|
let mut sb: raw::GC_stack_base = raw::GC_stack_base {
|
2024-05-09 12:41:54 -04:00
|
|
|
mem_base: null_mut(),
|
2024-03-19 14:43:01 +01:00
|
|
|
};
|
|
|
|
|
let r = raw::GC_get_stack_base(&mut sb);
|
|
|
|
|
if r as u32 != raw::GC_SUCCESS {
|
|
|
|
|
Err(anyhow::format_err!("GC_get_stack_base failed: {}", r))?;
|
|
|
|
|
}
|
|
|
|
|
raw::GC_register_my_thread(&sb);
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Drop for EvalState {
|
|
|
|
|
fn drop(&mut self) {
|
|
|
|
|
unsafe {
|
2024-05-09 12:29:32 -04:00
|
|
|
raw::state_free(self.raw_ptr());
|
2024-03-19 14:43:01 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
|
mod tests {
|
|
|
|
|
use ctor::ctor;
|
|
|
|
|
|
|
|
|
|
use super::*;
|
|
|
|
|
|
|
|
|
|
#[ctor]
|
|
|
|
|
fn setup() {
|
|
|
|
|
init().unwrap();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn eval_state_new_and_drop() {
|
|
|
|
|
gc_registering_current_thread(|| {
|
|
|
|
|
// very basic test: make sure initialization doesn't crash
|
|
|
|
|
let store = Store::open("auto").unwrap();
|
|
|
|
|
let _e = EvalState::new(store).unwrap();
|
|
|
|
|
})
|
|
|
|
|
.unwrap();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn eval_state_eval_from_string() {
|
|
|
|
|
gc_registering_current_thread(|| {
|
|
|
|
|
let store = Store::open("auto").unwrap();
|
|
|
|
|
let es = EvalState::new(store).unwrap();
|
2024-04-08 16:23:59 +02:00
|
|
|
let v = es.eval_from_string("1", "<test>").unwrap();
|
2024-03-19 14:43:01 +01:00
|
|
|
let v2 = v.clone();
|
|
|
|
|
es.force(&v).unwrap();
|
|
|
|
|
let t = es.value_type(&v).unwrap();
|
|
|
|
|
assert!(t == ValueType::Int);
|
|
|
|
|
let t2 = es.value_type(&v2).unwrap();
|
|
|
|
|
assert!(t2 == ValueType::Int);
|
|
|
|
|
gc_now();
|
|
|
|
|
})
|
|
|
|
|
.unwrap();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn eval_state_value_bool() {
|
|
|
|
|
gc_registering_current_thread(|| {
|
|
|
|
|
let store = Store::open("auto").unwrap();
|
|
|
|
|
let es = EvalState::new(store).unwrap();
|
2024-04-08 16:23:59 +02:00
|
|
|
let v = es.eval_from_string("true", "<test>").unwrap();
|
2024-03-19 14:43:01 +01:00
|
|
|
es.force(&v).unwrap();
|
|
|
|
|
let t = es.value_type(&v).unwrap();
|
|
|
|
|
assert!(t == ValueType::Bool);
|
|
|
|
|
})
|
|
|
|
|
.unwrap();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn eval_state_value_string() {
|
|
|
|
|
gc_registering_current_thread(|| {
|
|
|
|
|
let store = Store::open("auto").unwrap();
|
|
|
|
|
let es = EvalState::new(store).unwrap();
|
2024-04-08 16:23:59 +02:00
|
|
|
let v = es.eval_from_string("\"hello\"", "<test>").unwrap();
|
2024-03-19 14:43:01 +01:00
|
|
|
es.force(&v).unwrap();
|
|
|
|
|
let t = es.value_type(&v).unwrap();
|
|
|
|
|
assert!(t == ValueType::String);
|
2024-04-04 16:38:12 +02:00
|
|
|
let s = es.require_string(&v).unwrap();
|
|
|
|
|
assert!(s == "hello");
|
|
|
|
|
})
|
|
|
|
|
.unwrap();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn eval_state_value_string_unexpected_bool() {
|
|
|
|
|
gc_registering_current_thread(|| {
|
|
|
|
|
let store = Store::open("auto").unwrap();
|
|
|
|
|
let es = EvalState::new(store).unwrap();
|
2024-04-08 16:23:59 +02:00
|
|
|
let v = es.eval_from_string("true", "<test>").unwrap();
|
2024-04-04 16:38:12 +02:00
|
|
|
es.force(&v).unwrap();
|
|
|
|
|
let r = es.require_string(&v);
|
|
|
|
|
assert!(r.is_err());
|
|
|
|
|
// TODO: safe print value (like Nix would)
|
|
|
|
|
assert_eq!(
|
|
|
|
|
r.unwrap_err().to_string(),
|
|
|
|
|
"expected a string, but got a Bool"
|
|
|
|
|
);
|
|
|
|
|
})
|
|
|
|
|
.unwrap()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn eval_state_value_string_unexpected_path_value() {
|
|
|
|
|
gc_registering_current_thread(|| {
|
|
|
|
|
let store = Store::open("auto").unwrap();
|
|
|
|
|
let es = EvalState::new(store).unwrap();
|
2024-04-08 16:23:59 +02:00
|
|
|
let v = es.eval_from_string("/foo", "<test>").unwrap();
|
2024-04-04 16:38:12 +02:00
|
|
|
es.force(&v).unwrap();
|
|
|
|
|
let r = es.require_string(&v);
|
|
|
|
|
assert!(r.is_err());
|
|
|
|
|
assert_eq!(
|
|
|
|
|
r.unwrap_err().to_string(),
|
|
|
|
|
"expected a string, but got a Path"
|
|
|
|
|
);
|
|
|
|
|
})
|
|
|
|
|
.unwrap()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn eval_state_value_string_bad_utf() {
|
|
|
|
|
gc_registering_current_thread(|| {
|
|
|
|
|
let store = Store::open("auto").unwrap();
|
|
|
|
|
let es = EvalState::new(store).unwrap();
|
|
|
|
|
let v = es
|
2024-04-08 16:23:59 +02:00
|
|
|
.eval_from_string("builtins.substring 0 1 \"ü\"", "<test>")
|
2024-04-04 16:38:12 +02:00
|
|
|
.unwrap();
|
|
|
|
|
es.force(&v).unwrap();
|
|
|
|
|
let t = es.value_type(&v).unwrap();
|
|
|
|
|
assert!(t == ValueType::String);
|
|
|
|
|
let r = es.require_string(&v);
|
|
|
|
|
assert!(r.is_err());
|
|
|
|
|
assert!(r
|
|
|
|
|
.unwrap_err()
|
|
|
|
|
.to_string()
|
|
|
|
|
.contains("Nix string is not valid UTF-8"));
|
|
|
|
|
})
|
|
|
|
|
.unwrap();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn eval_state_value_string_unexpected_context() {
|
|
|
|
|
gc_registering_current_thread(|| {
|
|
|
|
|
let store = Store::open("auto").unwrap();
|
|
|
|
|
let es = EvalState::new(store).unwrap();
|
|
|
|
|
let v = es
|
2024-04-08 16:23:59 +02:00
|
|
|
.eval_from_string("(derivation { name = \"hello\"; system = \"dummy\"; builder = \"cmd.exe\"; }).outPath", "<test>")
|
2024-04-04 16:38:12 +02:00
|
|
|
.unwrap();
|
|
|
|
|
es.force(&v).unwrap();
|
|
|
|
|
let t = es.value_type(&v).unwrap();
|
|
|
|
|
assert!(t == ValueType::String);
|
|
|
|
|
// TODO
|
|
|
|
|
// let r = es.require_string_without_context(&v);
|
|
|
|
|
// assert!(r.is_err());
|
|
|
|
|
// assert!(r.unwrap_err().to_string().contains("unexpected context"));
|
2024-03-19 14:43:01 +01:00
|
|
|
})
|
|
|
|
|
.unwrap();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn eval_state_value_attrset() {
|
|
|
|
|
gc_registering_current_thread(|| {
|
|
|
|
|
let store = Store::open("auto").unwrap();
|
|
|
|
|
let es = EvalState::new(store).unwrap();
|
2024-04-08 16:23:59 +02:00
|
|
|
let v = es.eval_from_string("{ }", "<test>").unwrap();
|
2024-03-19 14:43:01 +01:00
|
|
|
es.force(&v).unwrap();
|
|
|
|
|
let t = es.value_type(&v).unwrap();
|
|
|
|
|
assert!(t == ValueType::AttrSet);
|
|
|
|
|
})
|
|
|
|
|
.unwrap();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn eval_state_value_list() {
|
|
|
|
|
gc_registering_current_thread(|| {
|
|
|
|
|
let store = Store::open("auto").unwrap();
|
|
|
|
|
let es = EvalState::new(store).unwrap();
|
|
|
|
|
let v = es
|
|
|
|
|
.eval_from_string("[ ]".to_string(), "<test>".to_string())
|
|
|
|
|
.unwrap();
|
|
|
|
|
es.force(&v).unwrap();
|
|
|
|
|
let t = es.value_type(&v).unwrap();
|
|
|
|
|
assert!(t == ValueType::List);
|
|
|
|
|
})
|
|
|
|
|
.unwrap();
|
|
|
|
|
}
|
|
|
|
|
}
|