1
0
Fork 0
mirror of https://github.com/NixOS/nix synced 2024-09-19 23:03:53 -04:00
nix/src/libexpr-c/nix_api_value.cc
2024-06-13 18:51:58 +02:00

664 lines
18 KiB
C++

#include "attr-set.hh"
#include "config.hh"
#include "eval.hh"
#include "globals.hh"
#include "path.hh"
#include "primops.hh"
#include "value.hh"
#include "nix_api_expr.h"
#include "nix_api_expr_internal.h"
#include "nix_api_util.h"
#include "nix_api_util_internal.h"
#include "nix_api_store_internal.h"
#include "nix_api_value.h"
#include "value/context.hh"
#ifdef HAVE_BOEHMGC
# include "gc/gc.h"
# define GC_INCLUDE_NEW 1
# include "gc_cpp.h"
#endif
// Internal helper functions to check [in] and [out] `Value *` parameters
static const nix::Value & check_value_not_null(const nix_value * value)
{
if (!value) {
throw std::runtime_error("nix_value is null");
}
return *((const nix::Value *) value);
}
static nix::Value & check_value_not_null(nix_value * value)
{
if (!value) {
throw std::runtime_error("nix_value is null");
}
return value->value;
}
static const nix::Value & check_value_in(const nix_value * value)
{
auto & v = check_value_not_null(value);
if (!v.isValid()) {
throw std::runtime_error("Uninitialized nix_value");
}
return v;
}
static nix::Value & check_value_in(nix_value * value)
{
auto & v = check_value_not_null(value);
if (!v.isValid()) {
throw std::runtime_error("Uninitialized nix_value");
}
return v;
}
static nix::Value & check_value_out(nix_value * value)
{
auto & v = check_value_not_null(value);
if (v.isValid()) {
throw std::runtime_error("nix_value already initialized. Variables are immutable");
}
return v;
}
static inline nix_value * as_nix_value_ptr(nix::Value * v)
{
return reinterpret_cast<nix_value *>(v);
}
/**
* Helper function to convert calls from nix into C API.
*
* Deals with errors and converts arguments from C++ into C types.
*/
static void nix_c_primop_wrapper(
PrimOpFun f, void * userdata, nix::EvalState & state, const nix::PosIdx pos, nix::Value ** args, nix::Value & v)
{
nix_c_context ctx;
// v currently has a thunk, but the C API initializers require an uninitialized value.
//
// We can't destroy the thunk, because that makes it impossible to retry,
// which is needed for tryEval and for evaluation drivers that evaluate more
// than one value (e.g. an attrset with two derivations, both of which
// reference v).
//
// Instead we create a temporary value, and then assign the result to v.
// This does not give the primop definition access to the thunk, but that's
// ok because we don't see a need for this yet (e.g. inspecting thunks,
// or maybe something to make blackholes work better; we don't know).
nix::Value vTmp;
f(userdata, &ctx, (EvalState *) &state, (nix_value **) args, (nix_value *) &vTmp);
if (ctx.last_err_code != NIX_OK) {
/* TODO: Throw different errors depending on the error code */
state.error<nix::EvalError>("Error from custom function: %s", *ctx.last_err).atPos(pos).debugThrow();
}
if (!vTmp.isValid()) {
state.error<nix::EvalError>("Implementation error in custom function: return value was not initialized")
.atPos(pos)
.debugThrow();
}
if (vTmp.type() == nix::nThunk) {
// We might allow this in the future if it makes sense for the evaluator
// e.g. implementing tail recursion by returning a thunk to the next
// "iteration". Until then, this is most likely a mistake or misunderstanding.
state.error<nix::EvalError>("Implementation error in custom function: return value must not be a thunk")
.atPos(pos)
.debugThrow();
}
v = vTmp;
}
PrimOp * nix_alloc_primop(
nix_c_context * context,
PrimOpFun fun,
int arity,
const char * name,
const char ** args,
const char * doc,
void * user_data)
{
if (context)
context->last_err_code = NIX_OK;
try {
using namespace std::placeholders;
auto p = new
#ifdef HAVE_BOEHMGC
(GC)
#endif
nix::PrimOp{
.name = name,
.args = {},
.arity = (size_t) arity,
.doc = doc,
.fun = std::bind(nix_c_primop_wrapper, fun, user_data, _1, _2, _3, _4)};
if (args)
for (size_t i = 0; args[i]; i++)
p->args.emplace_back(*args);
nix_gc_incref(nullptr, p);
return (PrimOp *) p;
}
NIXC_CATCH_ERRS_NULL
}
nix_err nix_register_primop(nix_c_context * context, PrimOp * primOp)
{
if (context)
context->last_err_code = NIX_OK;
try {
nix::RegisterPrimOp r(std::move(*((nix::PrimOp *) primOp)));
}
NIXC_CATCH_ERRS
}
nix_value * nix_alloc_value(nix_c_context * context, EvalState * state)
{
if (context)
context->last_err_code = NIX_OK;
try {
nix_value * res = as_nix_value_ptr(state->state.allocValue());
nix_gc_incref(nullptr, res);
return res;
}
NIXC_CATCH_ERRS_NULL
}
ValueType nix_get_type(nix_c_context * context, const nix_value * value)
{
if (context)
context->last_err_code = NIX_OK;
try {
auto & v = check_value_in(value);
using namespace nix;
switch (v.type()) {
case nThunk:
return NIX_TYPE_THUNK;
case nInt:
return NIX_TYPE_INT;
case nFloat:
return NIX_TYPE_FLOAT;
case nBool:
return NIX_TYPE_BOOL;
case nString:
return NIX_TYPE_STRING;
case nPath:
return NIX_TYPE_PATH;
case nNull:
return NIX_TYPE_NULL;
case nAttrs:
return NIX_TYPE_ATTRS;
case nList:
return NIX_TYPE_LIST;
case nFunction:
return NIX_TYPE_FUNCTION;
case nExternal:
return NIX_TYPE_EXTERNAL;
}
return NIX_TYPE_NULL;
}
NIXC_CATCH_ERRS_RES(NIX_TYPE_NULL);
}
const char * nix_get_typename(nix_c_context * context, const nix_value * value)
{
if (context)
context->last_err_code = NIX_OK;
try {
auto & v = check_value_in(value);
auto s = nix::showType(v);
return strdup(s.c_str());
}
NIXC_CATCH_ERRS_NULL
}
bool nix_get_bool(nix_c_context * context, const nix_value * value)
{
if (context)
context->last_err_code = NIX_OK;
try {
auto & v = check_value_in(value);
assert(v.type() == nix::nBool);
return v.boolean();
}
NIXC_CATCH_ERRS_RES(false);
}
nix_err
nix_get_string(nix_c_context * context, const nix_value * value, nix_get_string_callback callback, void * user_data)
{
if (context)
context->last_err_code = NIX_OK;
try {
auto & v = check_value_in(value);
assert(v.type() == nix::nString);
call_nix_get_string_callback(v.c_str(), callback, user_data);
}
NIXC_CATCH_ERRS
}
const char * nix_get_path_string(nix_c_context * context, const nix_value * value)
{
if (context)
context->last_err_code = NIX_OK;
try {
auto & v = check_value_in(value);
assert(v.type() == nix::nPath);
// NOTE (from @yorickvP)
// v._path.path should work but may not be how Eelco intended it.
// Long-term this function should be rewritten to copy some data into a
// user-allocated string.
// We could use v.path().to_string().c_str(), but I'm concerned this
// crashes. Looks like .path() allocates a CanonPath with a copy of the
// string, then it gets the underlying data from that.
return v.payload.path.path;
}
NIXC_CATCH_ERRS_NULL
}
unsigned int nix_get_list_size(nix_c_context * context, const nix_value * value)
{
if (context)
context->last_err_code = NIX_OK;
try {
auto & v = check_value_in(value);
assert(v.type() == nix::nList);
return v.listSize();
}
NIXC_CATCH_ERRS_RES(0);
}
unsigned int nix_get_attrs_size(nix_c_context * context, const nix_value * value)
{
if (context)
context->last_err_code = NIX_OK;
try {
auto & v = check_value_in(value);
assert(v.type() == nix::nAttrs);
return v.attrs()->size();
}
NIXC_CATCH_ERRS_RES(0);
}
double nix_get_float(nix_c_context * context, const nix_value * value)
{
if (context)
context->last_err_code = NIX_OK;
try {
auto & v = check_value_in(value);
assert(v.type() == nix::nFloat);
return v.fpoint();
}
NIXC_CATCH_ERRS_RES(0.0);
}
int64_t nix_get_int(nix_c_context * context, const nix_value * value)
{
if (context)
context->last_err_code = NIX_OK;
try {
auto & v = check_value_in(value);
assert(v.type() == nix::nInt);
return v.integer();
}
NIXC_CATCH_ERRS_RES(0);
}
ExternalValue * nix_get_external(nix_c_context * context, nix_value * value)
{
if (context)
context->last_err_code = NIX_OK;
try {
auto & v = check_value_out(value);
assert(v.type() == nix::nExternal);
return (ExternalValue *) v.external();
}
NIXC_CATCH_ERRS_NULL;
}
nix_value * nix_get_list_byidx(nix_c_context * context, const nix_value * value, EvalState * state, unsigned int ix)
{
if (context)
context->last_err_code = NIX_OK;
try {
auto & v = check_value_in(value);
assert(v.type() == nix::nList);
auto * p = v.listElems()[ix];
nix_gc_incref(nullptr, p);
if (p != nullptr)
state->state.forceValue(*p, nix::noPos);
return as_nix_value_ptr(p);
}
NIXC_CATCH_ERRS_NULL
}
nix_value * nix_get_attr_byname(nix_c_context * context, const nix_value * value, EvalState * state, const char * name)
{
if (context)
context->last_err_code = NIX_OK;
try {
auto & v = check_value_in(value);
assert(v.type() == nix::nAttrs);
nix::Symbol s = state->state.symbols.create(name);
auto attr = v.attrs()->get(s);
if (attr) {
nix_gc_incref(nullptr, attr->value);
state->state.forceValue(*attr->value, nix::noPos);
return as_nix_value_ptr(attr->value);
}
nix_set_err_msg(context, NIX_ERR_KEY, "missing attribute");
return nullptr;
}
NIXC_CATCH_ERRS_NULL
}
bool nix_has_attr_byname(nix_c_context * context, const nix_value * value, EvalState * state, const char * name)
{
if (context)
context->last_err_code = NIX_OK;
try {
auto & v = check_value_in(value);
assert(v.type() == nix::nAttrs);
nix::Symbol s = state->state.symbols.create(name);
auto attr = v.attrs()->get(s);
if (attr)
return true;
return false;
}
NIXC_CATCH_ERRS_RES(false);
}
nix_value * nix_get_attr_byidx(
nix_c_context * context, const nix_value * value, EvalState * state, unsigned int i, const char ** name)
{
if (context)
context->last_err_code = NIX_OK;
try {
auto & v = check_value_in(value);
const nix::Attr & a = (*v.attrs())[i];
*name = ((const std::string &) (state->state.symbols[a.name])).c_str();
nix_gc_incref(nullptr, a.value);
state->state.forceValue(*a.value, nix::noPos);
return as_nix_value_ptr(a.value);
}
NIXC_CATCH_ERRS_NULL
}
const char *
nix_get_attr_name_byidx(nix_c_context * context, const nix_value * value, EvalState * state, unsigned int i)
{
if (context)
context->last_err_code = NIX_OK;
try {
auto & v = check_value_in(value);
const nix::Attr & a = (*v.attrs())[i];
return ((const std::string &) (state->state.symbols[a.name])).c_str();
}
NIXC_CATCH_ERRS_NULL
}
nix_err nix_init_bool(nix_c_context * context, nix_value * value, bool b)
{
if (context)
context->last_err_code = NIX_OK;
try {
auto & v = check_value_out(value);
v.mkBool(b);
}
NIXC_CATCH_ERRS
}
// todo string context
nix_err nix_init_string(nix_c_context * context, nix_value * value, const char * str)
{
if (context)
context->last_err_code = NIX_OK;
try {
auto & v = check_value_out(value);
v.mkString(std::string_view(str));
}
NIXC_CATCH_ERRS
}
nix_err nix_init_path_string(nix_c_context * context, EvalState * s, nix_value * value, const char * str)
{
if (context)
context->last_err_code = NIX_OK;
try {
auto & v = check_value_out(value);
v.mkPath(s->state.rootPath(nix::CanonPath(str)));
}
NIXC_CATCH_ERRS
}
nix_err nix_init_float(nix_c_context * context, nix_value * value, double d)
{
if (context)
context->last_err_code = NIX_OK;
try {
auto & v = check_value_out(value);
v.mkFloat(d);
}
NIXC_CATCH_ERRS
}
nix_err nix_init_int(nix_c_context * context, nix_value * value, int64_t i)
{
if (context)
context->last_err_code = NIX_OK;
try {
auto & v = check_value_out(value);
v.mkInt(i);
}
NIXC_CATCH_ERRS
}
nix_err nix_init_null(nix_c_context * context, nix_value * value)
{
if (context)
context->last_err_code = NIX_OK;
try {
auto & v = check_value_out(value);
v.mkNull();
}
NIXC_CATCH_ERRS
}
nix_err nix_init_apply(nix_c_context * context, nix_value * value, nix_value * fn, nix_value * arg)
{
if (context)
context->last_err_code = NIX_OK;
try {
auto & v = check_value_not_null(value);
auto & f = check_value_not_null(fn);
auto & a = check_value_not_null(arg);
v.mkApp(&f, &a);
}
NIXC_CATCH_ERRS
}
nix_err nix_init_external(nix_c_context * context, nix_value * value, ExternalValue * val)
{
if (context)
context->last_err_code = NIX_OK;
try {
auto & v = check_value_out(value);
auto r = (nix::ExternalValueBase *) val;
v.mkExternal(r);
}
NIXC_CATCH_ERRS
}
ListBuilder * nix_make_list_builder(nix_c_context * context, EvalState * state, size_t capacity)
{
if (context)
context->last_err_code = NIX_OK;
try {
auto builder = state->state.buildList(capacity);
return new
#if HAVE_BOEHMGC
(NoGC)
#endif
ListBuilder{std::move(builder)};
}
NIXC_CATCH_ERRS_NULL
}
nix_err
nix_list_builder_insert(nix_c_context * context, ListBuilder * list_builder, unsigned int index, nix_value * value)
{
if (context)
context->last_err_code = NIX_OK;
try {
auto & e = check_value_not_null(value);
list_builder->builder[index] = &e;
}
NIXC_CATCH_ERRS
}
void nix_list_builder_free(ListBuilder * list_builder)
{
#if HAVE_BOEHMGC
GC_FREE(list_builder);
#else
delete list_builder;
#endif
}
nix_err nix_make_list(nix_c_context * context, ListBuilder * list_builder, nix_value * value)
{
if (context)
context->last_err_code = NIX_OK;
try {
auto & v = check_value_out(value);
v.mkList(list_builder->builder);
}
NIXC_CATCH_ERRS
}
nix_err nix_init_primop(nix_c_context * context, nix_value * value, PrimOp * p)
{
if (context)
context->last_err_code = NIX_OK;
try {
auto & v = check_value_out(value);
v.mkPrimOp((nix::PrimOp *) p);
}
NIXC_CATCH_ERRS
}
nix_err nix_copy_value(nix_c_context * context, nix_value * value, const nix_value * source)
{
if (context)
context->last_err_code = NIX_OK;
try {
auto & v = check_value_out(value);
auto & s = check_value_in(source);
v = s;
}
NIXC_CATCH_ERRS
}
nix_err nix_make_attrs(nix_c_context * context, nix_value * value, BindingsBuilder * b)
{
if (context)
context->last_err_code = NIX_OK;
try {
auto & v = check_value_out(value);
v.mkAttrs(b->builder);
}
NIXC_CATCH_ERRS
}
BindingsBuilder * nix_make_bindings_builder(nix_c_context * context, EvalState * state, size_t capacity)
{
if (context)
context->last_err_code = NIX_OK;
try {
auto bb = state->state.buildBindings(capacity);
return new
#if HAVE_BOEHMGC
(NoGC)
#endif
BindingsBuilder{std::move(bb)};
}
NIXC_CATCH_ERRS_NULL
}
nix_err nix_bindings_builder_insert(nix_c_context * context, BindingsBuilder * bb, const char * name, nix_value * value)
{
if (context)
context->last_err_code = NIX_OK;
try {
auto & v = check_value_not_null(value);
nix::Symbol s = bb->builder.state.symbols.create(name);
bb->builder.insert(s, &v);
}
NIXC_CATCH_ERRS
}
void nix_bindings_builder_free(BindingsBuilder * bb)
{
#if HAVE_BOEHMGC
GC_FREE((nix::BindingsBuilder *) bb);
#else
delete (nix::BindingsBuilder *) bb;
#endif
}
nix_realised_string * nix_string_realise(nix_c_context * context, EvalState * state, nix_value * value, bool isIFD)
{
if (context)
context->last_err_code = NIX_OK;
try {
auto & v = check_value_in(value);
nix::NixStringContext stringContext;
auto rawStr = state->state.coerceToString(nix::noPos, v, stringContext, "while realising a string").toOwned();
nix::StorePathSet storePaths;
auto rewrites = state->state.realiseContext(stringContext, &storePaths);
auto s = nix::rewriteStrings(rawStr, rewrites);
// Convert to the C API StorePath type and convert to vector for index-based access
std::vector<StorePath> vec;
for (auto & sp : storePaths) {
vec.push_back(StorePath{sp});
}
return new nix_realised_string{.str = s, .storePaths = vec};
}
NIXC_CATCH_ERRS_NULL
}
void nix_realised_string_free(nix_realised_string * s)
{
delete s;
}
size_t nix_realised_string_get_buffer_size(nix_realised_string * s)
{
return s->str.size();
}
const char * nix_realised_string_get_buffer_start(nix_realised_string * s)
{
return s->str.data();
}
size_t nix_realised_string_get_store_path_count(nix_realised_string * s)
{
return s->storePaths.size();
}
const StorePath * nix_realised_string_get_store_path(nix_realised_string * s, size_t i)
{
return &s->storePaths[i];
}