mirror of
https://github.com/NixOS/nix
synced 2025-06-25 02:21:16 +02:00
This also bans various sneaking of negative numbers from the language into unsuspecting builtins as was exposed while auditing the consequences of changing the Nix language integer type to a newtype. It's unlikely that this change comprehensively ensures correctness when passing integers out of the Nix language and we should probably add a checked-narrowing function or something similar, but that's out of scope for the immediate change. During the development of this I found a few fun facts about the language: - You could overflow integers by converting from unsigned JSON values. - You could overflow unsigned integers by converting negative numbers into them when going into Nix config, into fetchTree, and into flake inputs. The flake inputs and Nix config cannot actually be tested properly since they both ban thunks, however, we put in checks anyway because it's possible these could somehow be used to do such shenanigans some other way. Note that Lix has banned Nix language integer overflows since the very first public beta, but threw a SIGILL about them because we run with -fsanitize=signed-overflow -fsanitize-undefined-trap-on-error in production builds. Since the Nix language uses signed integers, overflow was simply undefined behaviour, and since we defined that to trap, it did. Trapping on it was a bad UX, but we didn't even entirely notice that we had done this at all until it was reported as a bug a couple of months later (which is, to be fair, that flag working as intended), and it's got enough production time that, aside from code that is IMHO buggy (and which is, in any case, not in nixpkgs) such as https://git.lix.systems/lix-project/lix/issues/445, we don't think anyone doing anything reasonable actually depends on wrapping overflow. Even for weird use cases such as doing funny bit crimes, it doesn't make sense IMO to have wrapping behaviour, since two's complement arithmetic overflow behaviour is so *aggressively* not what you want for *any* kind of mathematics/algorithms. The Nix language exists for package management, a domain where bit crimes are already only dubiously in scope to begin with, and it makes a lot more sense for that domain for the integers to never lose precision, either by throwing errors if they would, or by being arbitrary-precision. Fixes: https://github.com/NixOS/nix/issues/10968 Original-CL: https://gerrit.lix.systems/c/lix/+/1596 Change-Id: I51f253840c4af2ea5422b8a420aa5fafbf8fae75
663 lines
18 KiB
C++
663 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"
|
|
|
|
#if 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
|
|
#if 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().value;
|
|
}
|
|
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 = 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 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];
|
|
}
|