Skip to content

Commit 7a642f2

Browse files
glittersharkRadvendii
authored andcommitted
Use hybrid C / Pascal strings in the evaluator
Replace the null-terminated C-style strings in Value with hybrid C / Pascal strings, where the length is stored in the allocation before the data, and there is still a null byte at the end for the sake of C interopt. Co-Authored-By: Taeer Bar-Yam <[email protected]>
1 parent cbe8ec7 commit 7a642f2

File tree

17 files changed

+255
-120
lines changed

17 files changed

+255
-120
lines changed

src/libexpr-c/nix_api_value.cc

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -254,7 +254,7 @@ const char * nix_get_path_string(nix_c_context * context, const nix_value * valu
254254
// We could use v.path().to_string().c_str(), but I'm concerned this
255255
// crashes. Looks like .path() allocates a CanonPath with a copy of the
256256
// string, then it gets the underlying data from that.
257-
return v.pathStr();
257+
return v.pathStr().view().data();
258258
}
259259
NIXC_CATCH_ERRS_NULL
260260
}
@@ -439,7 +439,7 @@ nix_get_attr_byidx(nix_c_context * context, nix_value * value, EvalState * state
439439
return nullptr;
440440
}
441441
const nix::Attr & a = (*v.attrs())[i];
442-
*name = state->state.symbols[a.name].c_str();
442+
*name = state->state.symbols[a.name].string_data().view().data();
443443
nix_gc_incref(nullptr, a.value);
444444
state->state.forceValue(*a.value, nix::noPos);
445445
return as_nix_value_ptr(a.value);
@@ -460,7 +460,7 @@ nix_value * nix_get_attr_byidx_lazy(
460460
return nullptr;
461461
}
462462
const nix::Attr & a = (*v.attrs())[i];
463-
*name = state->state.symbols[a.name].c_str();
463+
*name = state->state.symbols[a.name].string_data().view().data();
464464
nix_gc_incref(nullptr, a.value);
465465
// Note: intentionally NOT calling forceValue() to keep the attribute lazy
466466
return as_nix_value_ptr(a.value);
@@ -480,7 +480,7 @@ const char * nix_get_attr_name_byidx(nix_c_context * context, nix_value * value,
480480
return nullptr;
481481
}
482482
const nix::Attr & a = (*v.attrs())[i];
483-
return state->state.symbols[a.name].c_str();
483+
return state->state.symbols[a.name].string_data().view().data();
484484
}
485485
NIXC_CATCH_ERRS_NULL
486486
}

src/libexpr-test-support/include/nix/expr/tests/libexpr.hh

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,8 +104,10 @@ MATCHER(IsAttrs, "")
104104
MATCHER_P(IsStringEq, s, fmt("The string is equal to \"%1%\"", s))
105105
{
106106
if (arg.type() != nString) {
107+
*result_listener << "of non-String type " << arg.type();
107108
return false;
108109
}
110+
*result_listener << "string(" << arg.string_view() << ")";
109111
return arg.string_view() == s;
110112
}
111113

src/libexpr-tests/json.cc

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
#include "nix/expr/tests/libexpr.hh"
22
#include "nix/expr/value-to-json.hh"
3+
#include "nix/expr/static-string-data.hh"
34

45
namespace nix {
56
// Testing the conversion to JSON
@@ -54,15 +55,15 @@ TEST_F(JSONValueTest, IntNegative)
5455
TEST_F(JSONValueTest, String)
5556
{
5657
Value v;
57-
v.mkStringNoCopy("test");
58+
v.mkStringNoCopy("test"_sds);
5859
ASSERT_EQ(getJSONValue(v), "\"test\"");
5960
}
6061

6162
TEST_F(JSONValueTest, StringQuotes)
6263
{
6364
Value v;
6465

65-
v.mkStringNoCopy("test\"");
66+
v.mkStringNoCopy("test\""_sds);
6667
ASSERT_EQ(getJSONValue(v), "\"test\\\"\"");
6768
}
6869

src/libexpr-tests/value/print.cc

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
#include "nix/expr/tests/libexpr.hh"
2+
#include "nix/expr/static-string-data.hh"
23

34
#include "nix/expr/value.hh"
45
#include "nix/expr/print.hh"
@@ -35,14 +36,14 @@ TEST_F(ValuePrintingTests, tBool)
3536
TEST_F(ValuePrintingTests, tString)
3637
{
3738
Value vString;
38-
vString.mkStringNoCopy("some-string");
39+
vString.mkStringNoCopy("some-string"_sds);
3940
test(vString, "\"some-string\"");
4041
}
4142

4243
TEST_F(ValuePrintingTests, tPath)
4344
{
4445
Value vPath;
45-
vPath.mkStringNoCopy("/foo");
46+
vPath.mkStringNoCopy("/foo"_sds);
4647
test(vPath, "\"/foo\"");
4748
}
4849

@@ -289,10 +290,10 @@ TEST_F(StringPrintingTests, maxLengthTruncation)
289290
TEST_F(ValuePrintingTests, attrsTypeFirst)
290291
{
291292
Value vType;
292-
vType.mkStringNoCopy("puppy");
293+
vType.mkStringNoCopy("puppy"_sds);
293294

294295
Value vApple;
295-
vApple.mkStringNoCopy("apple");
296+
vApple.mkStringNoCopy("apple"_sds);
296297

297298
BindingsBuilder builder = state.buildBindings(10);
298299
builder.insert(state.symbols.create("type"), &vType);
@@ -333,15 +334,15 @@ TEST_F(ValuePrintingTests, ansiColorsBool)
333334
TEST_F(ValuePrintingTests, ansiColorsString)
334335
{
335336
Value v;
336-
v.mkStringNoCopy("puppy");
337+
v.mkStringNoCopy("puppy"_sds);
337338

338339
test(v, ANSI_MAGENTA "\"puppy\"" ANSI_NORMAL, PrintOptions{.ansiColors = true});
339340
}
340341

341342
TEST_F(ValuePrintingTests, ansiColorsStringElided)
342343
{
343344
Value v;
344-
v.mkStringNoCopy("puppy");
345+
v.mkStringNoCopy("puppy"_sds);
345346

346347
test(
347348
v,
@@ -389,7 +390,7 @@ TEST_F(ValuePrintingTests, ansiColorsAttrs)
389390
TEST_F(ValuePrintingTests, ansiColorsDerivation)
390391
{
391392
Value vDerivation;
392-
vDerivation.mkStringNoCopy("derivation");
393+
vDerivation.mkStringNoCopy("derivation"_sds);
393394

394395
BindingsBuilder builder = state.buildBindings(10);
395396
builder.insert(state.s.type, &vDerivation);
@@ -412,7 +413,7 @@ TEST_F(ValuePrintingTests, ansiColorsError)
412413
{
413414
Value throw_ = state.getBuiltin("throw");
414415
Value message;
415-
message.mkStringNoCopy("uh oh!");
416+
message.mkStringNoCopy("uh oh!"_sds);
416417
Value vError;
417418
vError.mkApp(&throw_, &message);
418419

@@ -429,12 +430,12 @@ TEST_F(ValuePrintingTests, ansiColorsDerivationError)
429430
{
430431
Value throw_ = state.getBuiltin("throw");
431432
Value message;
432-
message.mkStringNoCopy("uh oh!");
433+
message.mkStringNoCopy("uh oh!"_sds);
433434
Value vError;
434435
vError.mkApp(&throw_, &message);
435436

436437
Value vDerivation;
437-
vDerivation.mkStringNoCopy("derivation");
438+
vDerivation.mkStringNoCopy("derivation"_sds);
438439

439440
BindingsBuilder builder = state.buildBindings(10);
440441
builder.insert(state.s.type, &vDerivation);

src/libexpr-tests/value/value.cc

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
#include "nix/expr/value.hh"
2+
#include "nix/expr/static-string-data.hh"
23

34
#include "nix/store/tests/libstore.hh"
45
#include <gtest/gtest.h>
@@ -27,17 +28,17 @@ TEST_F(ValueTest, staticString)
2728
{
2829
Value vStr1;
2930
Value vStr2;
30-
vStr1.mkStringNoCopy("foo");
31-
vStr2.mkStringNoCopy("foo");
31+
vStr1.mkStringNoCopy("foo"_sds);
32+
vStr2.mkStringNoCopy("foo"_sds);
3233

33-
auto sd1 = vStr1.string_view();
34-
auto sd2 = vStr2.string_view();
34+
auto & sd1 = vStr1.string_data();
35+
auto & sd2 = vStr2.string_data();
3536

3637
// The strings should be the same
37-
ASSERT_EQ(sd1, sd2);
38+
ASSERT_EQ(sd1.view(), sd2.view());
3839

3940
// The strings should also be backed by the same (static) allocation
40-
ASSERT_EQ(sd1.data(), sd2.data());
41+
ASSERT_EQ(&sd1, &sd2);
4142
}
4243

4344
} // namespace nix

src/libexpr/eval-cache.cc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,7 @@ struct AttrDb
147147
for (auto * elem : *context) {
148148
if (!first)
149149
ctx.push_back(' ');
150-
ctx.append(elem);
150+
ctx.append(elem->view());
151151
first = false;
152152
}
153153
state->insertAttributeWithContext.use()(key.first)(symbols[key.second])(AttrType::String) (s) (ctx)

src/libexpr/eval.cc

Lines changed: 29 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
#include "nix/expr/primops.hh"
44
#include "nix/expr/print-options.hh"
55
#include "nix/expr/symbol-table.hh"
6+
#include "nix/expr/value.hh"
67
#include "nix/util/exit.hh"
78
#include "nix/util/types.hh"
89
#include "nix/util/util.hh"
@@ -28,6 +29,8 @@
2829
#include "parser-tab.hh"
2930

3031
#include <algorithm>
32+
#include <cstddef>
33+
#include <cstdlib>
3134
#include <iostream>
3235
#include <sstream>
3336
#include <cstring>
@@ -48,28 +51,22 @@ using json = nlohmann::json;
4851

4952
namespace nix {
5053

51-
static char * allocString(size_t size)
54+
StringData & StringData::alloc(size_t size)
5255
{
53-
char * t;
54-
t = (char *) GC_MALLOC_ATOMIC(size);
55-
if (!t)
56+
auto res = (StringData *) GC_MALLOC_ATOMIC(sizeof(size_t) + size + 1);
57+
if (!res) {
5658
throw std::bad_alloc();
57-
return t;
59+
}
60+
res->m_size = size;
61+
return *res;
5862
}
5963

60-
// When there's no need to write to the string, we can optimize away empty
61-
// string allocations.
62-
// This function handles makeImmutableString(std::string_view()) by returning
63-
// the empty string.
64-
static const char * makeImmutableString(std::string_view s)
64+
StringData & StringData::make(std::string_view s)
6565
{
66-
const size_t size = s.size();
67-
if (size == 0)
68-
return "";
69-
auto t = allocString(size + 1);
70-
memcpy(t, s.data(), size);
71-
t[size] = '\0';
72-
return t;
66+
auto & res = alloc(s.size());
67+
memcpy(&res.m_data, s.data(), s.size());
68+
res.m_data[s.size()] = '\0';
69+
return res;
7370
}
7471

7572
RootValue allocRootValue(Value * v)
@@ -585,7 +582,7 @@ std::optional<EvalState::Doc> EvalState::getDoc(Value & v)
585582
.name = name,
586583
.arity = 0, // FIXME: figure out how deep by syntax only? It's not semantically useful though...
587584
.args = {},
588-
.doc = makeImmutableString(s.view()), // NOTE: memory leak when compiled without GC
585+
.doc = StringData::make(s.view()).view().data(), // NOTE: memory leak when compiled without GC
589586
};
590587
}
591588
if (isFunctor(v)) {
@@ -819,7 +816,7 @@ DebugTraceStacker::DebugTraceStacker(EvalState & evalState, DebugTrace t)
819816

820817
void Value::mkString(std::string_view s)
821818
{
822-
mkStringNoCopy(makeImmutableString(s));
819+
mkStringNoCopy(StringData::make(s));
823820
}
824821

825822
Value::StringWithContext::Context * Value::StringWithContext::Context::fromBuilder(const NixStringContext & context)
@@ -829,23 +826,23 @@ Value::StringWithContext::Context * Value::StringWithContext::Context::fromBuild
829826

830827
auto ctx = new (allocBytes(sizeof(Context) + context.size() * sizeof(value_type))) Context(context.size());
831828
std::ranges::transform(
832-
context, ctx->elems, [](const NixStringContextElem & elt) { return makeImmutableString(elt.to_string()); });
829+
context, ctx->elems, [](const NixStringContextElem & elt) { return &StringData::make(elt.to_string()); });
833830
return ctx;
834831
}
835832

836833
void Value::mkString(std::string_view s, const NixStringContext & context)
837834
{
838-
mkStringNoCopy(makeImmutableString(s), Value::StringWithContext::Context::fromBuilder(context));
835+
mkStringNoCopy(StringData::make(s), Value::StringWithContext::Context::fromBuilder(context));
839836
}
840837

841-
void Value::mkStringMove(const char * s, const NixStringContext & context)
838+
void Value::mkStringMove(const StringData & s, const NixStringContext & context)
842839
{
843840
mkStringNoCopy(s, Value::StringWithContext::Context::fromBuilder(context));
844841
}
845842

846843
void Value::mkPath(const SourcePath & path)
847844
{
848-
mkPath(&*path.accessor, makeImmutableString(path.path.abs()));
845+
mkPath(&*path.accessor, StringData::make(path.path.abs()));
849846
}
850847

851848
inline Value * EvalState::lookupVar(Env * env, const ExprVar & var, bool noEval)
@@ -1558,6 +1555,7 @@ void EvalState::callFunction(Value & fun, std::span<Value *> args, Value & vRes,
15581555
.withFrame(*vCur.lambda().env, lambda)
15591556
.debugThrow();
15601557
}
1558+
15611559
unreachable();
15621560
}
15631561
} else {
@@ -2094,25 +2092,27 @@ void ExprConcatStrings::eval(EvalState & state, Env & env, Value & v)
20942092
} else if (firstType == nFloat) {
20952093
v.mkFloat(nf);
20962094
} else if (firstType == nPath) {
2097-
if (!context.empty())
2095+
if (!context.empty()) {
20982096
state.error<EvalError>("a string that refers to a store path cannot be appended to a path")
20992097
.atPos(pos)
21002098
.withFrame(env, *this)
21012099
.debugThrow();
2100+
}
2101+
21022102
std::string result_str;
21032103
result_str.reserve(sSize);
21042104
for (const auto & part : strings) {
21052105
result_str += *part;
21062106
}
21072107
v.mkPath(state.rootPath(CanonPath(result_str)));
21082108
} else {
2109-
char * result_str = allocString(sSize + 1);
2110-
char * tmp = result_str;
2109+
auto & result_str = StringData::alloc(sSize);
2110+
auto * tmp = result_str.data();
21112111
for (const auto & part : strings) {
21122112
memcpy(tmp, part->data(), part->size());
21132113
tmp += part->size();
21142114
}
2115-
*tmp = 0;
2115+
*tmp = '\0';
21162116
v.mkStringMove(result_str, context);
21172117
}
21182118
}
@@ -2288,7 +2288,7 @@ void copyContext(const Value & v, NixStringContext & context, const Experimental
22882288
{
22892289
if (auto * ctx = v.context())
22902290
for (auto * elem : *ctx)
2291-
context.insert(NixStringContextElem::parse(elem, xpSettings));
2291+
context.insert(NixStringContextElem::parse(elem->view(), xpSettings));
22922292
}
22932293

22942294
std::string_view EvalState::forceString(
@@ -2370,7 +2370,7 @@ BackedStringView EvalState::coerceToString(
23702370
if (!canonicalizePath && !copyToStore) {
23712371
// FIXME: hack to preserve path literals that end in a
23722372
// slash, as in /foo/${x}.
2373-
return v.pathStrView();
2373+
return v.pathStr().view();
23742374
} else if (copyToStore) {
23752375
return store->printStorePath(copyPathToStore(context, v.path()));
23762376
} else {

src/libexpr/include/nix/expr/eval-cache.hh

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,11 @@ struct int_t
7979

8080
typedef uint64_t AttrId;
8181
typedef std::pair<AttrId, Symbol> AttrKey;
82-
typedef std::pair<std::string, NixStringContext> string_t;
82+
typedef std::pair<
83+
// XXX aspen: StringData?
84+
std::string,
85+
NixStringContext>
86+
string_t;
8387

8488
typedef std::variant<
8589
std::vector<Symbol>,

src/libexpr/include/nix/expr/meson.build

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ headers = [ config_pub_h ] + files(
3131
'print.hh',
3232
'repl-exit-status.hh',
3333
'search-path.hh',
34+
'static-string-data.hh',
3435
'symbol-table.hh',
3536
'value-to-json.hh',
3637
'value-to-xml.hh',

0 commit comments

Comments
 (0)