Skip to content

Commit 2f15843

Browse files
glittersharkEricson2314
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.
1 parent ef88936 commit 2f15843

File tree

14 files changed

+231
-116
lines changed

14 files changed

+231
-116
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: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,15 +54,15 @@ TEST_F(JSONValueTest, IntNegative)
5454
TEST_F(JSONValueTest, String)
5555
{
5656
Value v;
57-
v.mkStringNoCopy("test");
57+
v.mkStringNoCopy("test"_sds);
5858
ASSERT_EQ(getJSONValue(v), "\"test\"");
5959
}
6060

6161
TEST_F(JSONValueTest, StringQuotes)
6262
{
6363
Value v;
6464

65-
v.mkStringNoCopy("test\"");
65+
v.mkStringNoCopy("test\""_sds);
6666
ASSERT_EQ(getJSONValue(v), "\"test\\\"\"");
6767
}
6868

src/libexpr-tests/value/print.cc

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -35,14 +35,14 @@ TEST_F(ValuePrintingTests, tBool)
3535
TEST_F(ValuePrintingTests, tString)
3636
{
3737
Value vString;
38-
vString.mkStringNoCopy("some-string");
38+
vString.mkStringNoCopy("some-string"_sds);
3939
test(vString, "\"some-string\"");
4040
}
4141

4242
TEST_F(ValuePrintingTests, tPath)
4343
{
4444
Value vPath;
45-
vPath.mkStringNoCopy("/foo");
45+
vPath.mkStringNoCopy("/foo"_sds);
4646
test(vPath, "\"/foo\"");
4747
}
4848

@@ -289,10 +289,10 @@ TEST_F(StringPrintingTests, maxLengthTruncation)
289289
TEST_F(ValuePrintingTests, attrsTypeFirst)
290290
{
291291
Value vType;
292-
vType.mkStringNoCopy("puppy");
292+
vType.mkStringNoCopy("puppy"_sds);
293293

294294
Value vApple;
295-
vApple.mkStringNoCopy("apple");
295+
vApple.mkStringNoCopy("apple"_sds);
296296

297297
BindingsBuilder builder = state.buildBindings(10);
298298
builder.insert(state.symbols.create("type"), &vType);
@@ -333,15 +333,15 @@ TEST_F(ValuePrintingTests, ansiColorsBool)
333333
TEST_F(ValuePrintingTests, ansiColorsString)
334334
{
335335
Value v;
336-
v.mkStringNoCopy("puppy");
336+
v.mkStringNoCopy("puppy"_sds);
337337

338338
test(v, ANSI_MAGENTA "\"puppy\"" ANSI_NORMAL, PrintOptions{.ansiColors = true});
339339
}
340340

341341
TEST_F(ValuePrintingTests, ansiColorsStringElided)
342342
{
343343
Value v;
344-
v.mkStringNoCopy("puppy");
344+
v.mkStringNoCopy("puppy"_sds);
345345

346346
test(
347347
v,
@@ -389,7 +389,7 @@ TEST_F(ValuePrintingTests, ansiColorsAttrs)
389389
TEST_F(ValuePrintingTests, ansiColorsDerivation)
390390
{
391391
Value vDerivation;
392-
vDerivation.mkStringNoCopy("derivation");
392+
vDerivation.mkStringNoCopy("derivation"_sds);
393393

394394
BindingsBuilder builder = state.buildBindings(10);
395395
builder.insert(state.s.type, &vDerivation);
@@ -412,7 +412,7 @@ TEST_F(ValuePrintingTests, ansiColorsError)
412412
{
413413
Value throw_ = state.getBuiltin("throw");
414414
Value message;
415-
message.mkStringNoCopy("uh oh!");
415+
message.mkStringNoCopy("uh oh!"_sds);
416416
Value vError;
417417
vError.mkApp(&throw_, &message);
418418

@@ -429,12 +429,12 @@ TEST_F(ValuePrintingTests, ansiColorsDerivationError)
429429
{
430430
Value throw_ = state.getBuiltin("throw");
431431
Value message;
432-
message.mkStringNoCopy("uh oh!");
432+
message.mkStringNoCopy("uh oh!"_sds);
433433
Value vError;
434434
vError.mkApp(&throw_, &message);
435435

436436
Value vDerivation;
437-
vDerivation.mkStringNoCopy("derivation");
437+
vDerivation.mkStringNoCopy("derivation"_sds);
438438

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

src/libexpr-tests/value/value.cc

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -27,17 +27,17 @@ TEST_F(ValueTest, staticString)
2727
{
2828
Value vStr1;
2929
Value vStr2;
30-
vStr1.mkStringNoCopy("foo");
31-
vStr2.mkStringNoCopy("foo");
30+
vStr1.mkStringNoCopy("foo"_sds);
31+
vStr2.mkStringNoCopy("foo"_sds);
3232

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

3636
// The strings should be the same
37-
ASSERT_EQ(sd1, sd2);
37+
ASSERT_EQ(sd1.view(), sd2.view());
3838

3939
// The strings should also be backed by the same (static) allocation
40-
ASSERT_EQ(std::bit_cast<size_t>(sd1.data()), std::bit_cast<size_t>(sd2.data()));
40+
ASSERT_EQ(std::bit_cast<size_t>(sd1), std::bit_cast<size_t>(sd2));
4141
}
4242

4343
} // namespace nix

src/libexpr/eval.cc

Lines changed: 28 additions & 28 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>
@@ -47,28 +50,22 @@ using json = nlohmann::json;
4750

4851
namespace nix {
4952

50-
static char * allocString(size_t size)
53+
StringData & StringData::alloc(size_t size)
5154
{
52-
char * t;
53-
t = (char *) GC_MALLOC_ATOMIC(size);
54-
if (!t)
55+
auto res = (StringData *) GC_MALLOC_ATOMIC(sizeof(size_t) + size + 1);
56+
if (!res) {
5557
throw std::bad_alloc();
56-
return t;
58+
}
59+
res->m_size = size;
60+
return *res;
5761
}
5862

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

7471
RootValue allocRootValue(Value * v)
@@ -584,7 +581,7 @@ std::optional<EvalState::Doc> EvalState::getDoc(Value & v)
584581
.name = name,
585582
.arity = 0, // FIXME: figure out how deep by syntax only? It's not semantically useful though...
586583
.args = {},
587-
.doc = makeImmutableString(s.view()), // NOTE: memory leak when compiled without GC
584+
.doc = StringData::make(s.view()).view().data(), // NOTE: memory leak when compiled without GC
588585
};
589586
}
590587
if (isFunctor(v)) {
@@ -818,7 +815,7 @@ DebugTraceStacker::DebugTraceStacker(EvalState & evalState, DebugTrace t)
818815

819816
void Value::mkString(std::string_view s)
820817
{
821-
mkStringNoCopy(makeImmutableString(s));
818+
mkStringNoCopy(StringData::make(s));
822819
}
823820

824821
static const char ** encodeContext(const NixStringContext & context)
@@ -827,7 +824,7 @@ static const char ** encodeContext(const NixStringContext & context)
827824
size_t n = 0;
828825
auto ctx = (const char **) allocBytes((context.size() + 1) * sizeof(char *));
829826
for (auto & i : context) {
830-
ctx[n++] = makeImmutableString({i.to_string()});
827+
ctx[n++] = StringData::make({i.to_string()}).view().data();
831828
}
832829
ctx[n] = nullptr;
833830
return ctx;
@@ -837,17 +834,17 @@ static const char ** encodeContext(const NixStringContext & context)
837834

838835
void Value::mkString(std::string_view s, const NixStringContext & context)
839836
{
840-
mkStringNoCopy(makeImmutableString(s), encodeContext(context));
837+
mkStringNoCopy(StringData::make(s), encodeContext(context));
841838
}
842839

843-
void Value::mkStringMove(const char * s, const NixStringContext & context)
840+
void Value::mkStringMove(const StringData & s, const NixStringContext & context)
844841
{
845842
mkStringNoCopy(s, encodeContext(context));
846843
}
847844

848845
void Value::mkPath(const SourcePath & path)
849846
{
850-
mkPath(&*path.accessor, makeImmutableString(path.path.abs()));
847+
mkPath(&*path.accessor, StringData::make(path.path.abs()));
851848
}
852849

853850
inline Value * EvalState::lookupVar(Env * env, const ExprVar & var, bool noEval)
@@ -1559,6 +1556,7 @@ void EvalState::callFunction(Value & fun, std::span<Value *> args, Value & vRes,
15591556
.withFrame(*vCur.lambda().env, lambda)
15601557
.debugThrow();
15611558
}
1559+
15621560
unreachable();
15631561
}
15641562
} else {
@@ -2095,25 +2093,27 @@ void ExprConcatStrings::eval(EvalState & state, Env & env, Value & v)
20952093
} else if (firstType == nFloat) {
20962094
v.mkFloat(nf);
20972095
} else if (firstType == nPath) {
2098-
if (!context.empty())
2096+
if (!context.empty()) {
20992097
state.error<EvalError>("a string that refers to a store path cannot be appended to a path")
21002098
.atPos(pos)
21012099
.withFrame(env, *this)
21022100
.debugThrow();
2101+
}
2102+
21032103
std::string result_str;
21042104
result_str.reserve(sSize);
21052105
for (const auto & part : strings) {
21062106
result_str += *part;
21072107
}
21082108
v.mkPath(state.rootPath(CanonPath(result_str)));
21092109
} else {
2110-
char * result_str = allocString(sSize + 1);
2111-
char * tmp = result_str;
2110+
auto & result_str = StringData::alloc(sSize);
2111+
auto tmp = result_str.data();
21122112
for (const auto & part : strings) {
21132113
memcpy(tmp, part->data(), part->size());
21142114
tmp += part->size();
21152115
}
2116-
*tmp = 0;
2116+
*tmp = '\0';
21172117
v.mkStringMove(result_str, context);
21182118
}
21192119
}
@@ -2369,7 +2369,7 @@ BackedStringView EvalState::coerceToString(
23692369
if (!canonicalizePath && !copyToStore) {
23702370
// FIXME: hack to preserve path literals that end in a
23712371
// slash, as in /foo/${x}.
2372-
return v.pathStrView();
2372+
return v.pathStr().view();
23732373
} else if (copyToStore) {
23742374
return store->printStorePath(copyPathToStore(context, v.path()));
23752375
} 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/nixexpr.hh

Lines changed: 6 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
#include <map>
55
#include <span>
6+
#include <memory>
67
#include <vector>
78
#include <memory_resource>
89
#include <algorithm>
@@ -193,22 +194,18 @@ struct ExprString : Expr
193194
* This is only for strings already allocated in our polymorphic allocator,
194195
* or that live at least that long (e.g. c++ string literals)
195196
*/
196-
ExprString(const char * s)
197+
ExprString(const StringData & s)
197198
{
198199
v.mkStringNoCopy(s);
199200
};
200201

201202
ExprString(std::pmr::polymorphic_allocator<char> & alloc, std::string_view sv)
202203
{
203-
auto len = sv.length();
204-
if (len == 0) {
205-
v.mkStringNoCopy("");
204+
if (sv.size() == 0) {
205+
v.mkStringNoCopy(""_sds);
206206
return;
207207
}
208-
char * s = alloc.allocate(len + 1);
209-
sv.copy(s, len);
210-
s[len] = '\0';
211-
v.mkStringNoCopy(s);
208+
v.mkStringNoCopy(StringData::make(*alloc.resource(), sv));
212209
};
213210

214211
Value * maybeThunk(EvalState & state, Env & env) override;
@@ -223,11 +220,7 @@ struct ExprPath : Expr
223220
ExprPath(std::pmr::polymorphic_allocator<char> & alloc, ref<SourceAccessor> accessor, std::string_view sv)
224221
: accessor(accessor)
225222
{
226-
auto len = sv.length();
227-
char * s = alloc.allocate(len + 1);
228-
sv.copy(s, len);
229-
s[len] = '\0';
230-
v.mkPath(&*accessor, s);
223+
v.mkPath(&*accessor, StringData::make(*alloc.resource(), sv));
231224
}
232225

233226
Value * maybeThunk(EvalState & state, Env & env) override;

0 commit comments

Comments
 (0)