1
1
Fork 0
mirror of https://github.com/NixOS/nix.git synced 2025-11-11 04:56:01 +01:00

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 <taeer@bar-yam.me>
Co-Authored-By: Sergei Zimmerman <sergei@zimmerman.foo>
This commit is contained in:
Aspen Smith 2025-09-12 20:45:20 -04:00 committed by John Ericson
parent 8c113f80f3
commit 3bf8c76072
14 changed files with 279 additions and 100 deletions

View file

@ -1,5 +1,6 @@
#include "nix/expr/tests/libexpr.hh"
#include "nix/expr/value-to-json.hh"
#include "nix/expr/static-string-data.hh"
namespace nix {
// Testing the conversion to JSON
@ -54,7 +55,7 @@ TEST_F(JSONValueTest, IntNegative)
TEST_F(JSONValueTest, String)
{
Value v;
v.mkStringNoCopy("test");
v.mkStringNoCopy("test"_sds);
ASSERT_EQ(getJSONValue(v), "\"test\"");
}
@ -62,7 +63,7 @@ TEST_F(JSONValueTest, StringQuotes)
{
Value v;
v.mkStringNoCopy("test\"");
v.mkStringNoCopy("test\""_sds);
ASSERT_EQ(getJSONValue(v), "\"test\\\"\"");
}

View file

@ -1,4 +1,5 @@
#include "nix/expr/tests/libexpr.hh"
#include "nix/expr/static-string-data.hh"
#include "nix/expr/value.hh"
#include "nix/expr/print.hh"
@ -35,14 +36,14 @@ TEST_F(ValuePrintingTests, tBool)
TEST_F(ValuePrintingTests, tString)
{
Value vString;
vString.mkStringNoCopy("some-string");
vString.mkStringNoCopy("some-string"_sds);
test(vString, "\"some-string\"");
}
TEST_F(ValuePrintingTests, tPath)
{
Value vPath;
vPath.mkStringNoCopy("/foo");
vPath.mkStringNoCopy("/foo"_sds);
test(vPath, "\"/foo\"");
}
@ -289,10 +290,10 @@ TEST_F(StringPrintingTests, maxLengthTruncation)
TEST_F(ValuePrintingTests, attrsTypeFirst)
{
Value vType;
vType.mkStringNoCopy("puppy");
vType.mkStringNoCopy("puppy"_sds);
Value vApple;
vApple.mkStringNoCopy("apple");
vApple.mkStringNoCopy("apple"_sds);
BindingsBuilder builder = state.buildBindings(10);
builder.insert(state.symbols.create("type"), &vType);
@ -333,7 +334,7 @@ TEST_F(ValuePrintingTests, ansiColorsBool)
TEST_F(ValuePrintingTests, ansiColorsString)
{
Value v;
v.mkStringNoCopy("puppy");
v.mkStringNoCopy("puppy"_sds);
test(v, ANSI_MAGENTA "\"puppy\"" ANSI_NORMAL, PrintOptions{.ansiColors = true});
}
@ -341,7 +342,7 @@ TEST_F(ValuePrintingTests, ansiColorsString)
TEST_F(ValuePrintingTests, ansiColorsStringElided)
{
Value v;
v.mkStringNoCopy("puppy");
v.mkStringNoCopy("puppy"_sds);
test(
v,
@ -389,7 +390,7 @@ TEST_F(ValuePrintingTests, ansiColorsAttrs)
TEST_F(ValuePrintingTests, ansiColorsDerivation)
{
Value vDerivation;
vDerivation.mkStringNoCopy("derivation");
vDerivation.mkStringNoCopy("derivation"_sds);
BindingsBuilder builder = state.buildBindings(10);
builder.insert(state.s.type, &vDerivation);
@ -412,7 +413,7 @@ TEST_F(ValuePrintingTests, ansiColorsError)
{
Value throw_ = state.getBuiltin("throw");
Value message;
message.mkStringNoCopy("uh oh!");
message.mkStringNoCopy("uh oh!"_sds);
Value vError;
vError.mkApp(&throw_, &message);
@ -429,12 +430,12 @@ TEST_F(ValuePrintingTests, ansiColorsDerivationError)
{
Value throw_ = state.getBuiltin("throw");
Value message;
message.mkStringNoCopy("uh oh!");
message.mkStringNoCopy("uh oh!"_sds);
Value vError;
vError.mkApp(&throw_, &message);
Value vDerivation;
vDerivation.mkStringNoCopy("derivation");
vDerivation.mkStringNoCopy("derivation"_sds);
BindingsBuilder builder = state.buildBindings(10);
builder.insert(state.s.type, &vDerivation);

View file

@ -1,4 +1,5 @@
#include "nix/expr/value.hh"
#include "nix/expr/static-string-data.hh"
#include "nix/store/tests/libstore.hh"
#include <gtest/gtest.h>
@ -27,17 +28,17 @@ TEST_F(ValueTest, staticString)
{
Value vStr1;
Value vStr2;
vStr1.mkStringNoCopy("foo");
vStr2.mkStringNoCopy("foo");
vStr1.mkStringNoCopy("foo"_sds);
vStr2.mkStringNoCopy("foo"_sds);
auto sd1 = vStr1.string_view();
auto sd2 = vStr2.string_view();
auto & sd1 = vStr1.string_data();
auto & sd2 = vStr2.string_data();
// The strings should be the same
ASSERT_EQ(sd1, sd2);
ASSERT_EQ(sd1.view(), sd2.view());
// The strings should also be backed by the same (static) allocation
ASSERT_EQ(sd1.data(), sd2.data());
ASSERT_EQ(&sd1, &sd2);
}
} // namespace nix

View file

@ -147,7 +147,7 @@ struct AttrDb
for (auto * elem : *context) {
if (!first)
ctx.push_back(' ');
ctx.append(elem);
ctx.append(elem->view());
first = false;
}
state->insertAttributeWithContext.use()(key.first)(symbols[key.second])(AttrType::String) (s) (ctx)

View file

@ -3,6 +3,7 @@
#include "nix/expr/primops.hh"
#include "nix/expr/print-options.hh"
#include "nix/expr/symbol-table.hh"
#include "nix/expr/value.hh"
#include "nix/util/exit.hh"
#include "nix/util/types.hh"
#include "nix/util/util.hh"
@ -28,6 +29,8 @@
#include "parser-tab.hh"
#include <algorithm>
#include <cstddef>
#include <cstdlib>
#include <iostream>
#include <sstream>
#include <cstring>
@ -48,6 +51,9 @@ using json = nlohmann::json;
namespace nix {
/**
* Just for doc strings. Not for regular string values.
*/
static char * allocString(size_t size)
{
char * t;
@ -61,6 +67,9 @@ static char * allocString(size_t size)
// string allocations.
// This function handles makeImmutableString(std::string_view()) by returning
// the empty string.
/**
* Just for doc strings. Not for regular string values.
*/
static const char * makeImmutableString(std::string_view s)
{
const size_t size = s.size();
@ -72,6 +81,25 @@ static const char * makeImmutableString(std::string_view s)
return t;
}
StringData & StringData::alloc(size_t size)
{
void * t = GC_MALLOC_ATOMIC(sizeof(StringData) + size + 1);
if (!t)
throw std::bad_alloc();
auto res = new (t) StringData(size);
return *res;
}
const StringData & StringData::make(std::string_view s)
{
if (s.empty())
return ""_sds;
auto & res = alloc(s.size());
std::memcpy(&res.data_, s.data(), s.size());
res.data_[s.size()] = '\0';
return res;
}
RootValue allocRootValue(Value * v)
{
return std::allocate_shared<Value *>(traceable_allocator<Value *>(), v);
@ -585,7 +613,9 @@ std::optional<EvalState::Doc> EvalState::getDoc(Value & v)
.name = name,
.arity = 0, // FIXME: figure out how deep by syntax only? It's not semantically useful though...
.args = {},
.doc = makeImmutableString(s.view()), // NOTE: memory leak when compiled without GC
/* N.B. Can't use StringData here, because that would lead to an interior pointer.
NOTE: memory leak when compiled without GC. */
.doc = makeImmutableString(s.view()),
};
}
if (isFunctor(v)) {
@ -819,7 +849,7 @@ DebugTraceStacker::DebugTraceStacker(EvalState & evalState, DebugTrace t)
void Value::mkString(std::string_view s)
{
mkStringNoCopy(makeImmutableString(s));
mkStringNoCopy(StringData::make(s));
}
Value::StringWithContext::Context * Value::StringWithContext::Context::fromBuilder(const NixStringContext & context)
@ -829,23 +859,23 @@ Value::StringWithContext::Context * Value::StringWithContext::Context::fromBuild
auto ctx = new (allocBytes(sizeof(Context) + context.size() * sizeof(value_type))) Context(context.size());
std::ranges::transform(
context, ctx->elems, [](const NixStringContextElem & elt) { return makeImmutableString(elt.to_string()); });
context, ctx->elems, [](const NixStringContextElem & elt) { return &StringData::make(elt.to_string()); });
return ctx;
}
void Value::mkString(std::string_view s, const NixStringContext & context)
{
mkStringNoCopy(makeImmutableString(s), Value::StringWithContext::Context::fromBuilder(context));
mkStringNoCopy(StringData::make(s), Value::StringWithContext::Context::fromBuilder(context));
}
void Value::mkStringMove(const char * s, const NixStringContext & context)
void Value::mkStringMove(const StringData & s, const NixStringContext & context)
{
mkStringNoCopy(s, Value::StringWithContext::Context::fromBuilder(context));
}
void Value::mkPath(const SourcePath & path)
{
mkPath(&*path.accessor, makeImmutableString(path.path.abs()));
mkPath(&*path.accessor, StringData::make(path.path.abs()));
}
inline Value * EvalState::lookupVar(Env * env, const ExprVar & var, bool noEval)
@ -2099,21 +2129,21 @@ void ExprConcatStrings::eval(EvalState & state, Env & env, Value & v)
.atPos(pos)
.withFrame(env, *this)
.debugThrow();
std::string result_str;
result_str.reserve(sSize);
std::string resultStr;
resultStr.reserve(sSize);
for (const auto & part : strings) {
result_str += *part;
resultStr += *part;
}
v.mkPath(state.rootPath(CanonPath(result_str)));
v.mkPath(state.rootPath(CanonPath(resultStr)));
} else {
char * result_str = allocString(sSize + 1);
char * tmp = result_str;
auto & resultStr = StringData::alloc(sSize);
auto * tmp = resultStr.data();
for (const auto & part : strings) {
memcpy(tmp, part->data(), part->size());
std::memcpy(tmp, part->data(), part->size());
tmp += part->size();
}
*tmp = 0;
v.mkStringMove(result_str, context);
*tmp = '\0';
v.mkStringMove(resultStr, context);
}
}
@ -2288,7 +2318,7 @@ void copyContext(const Value & v, NixStringContext & context, const Experimental
{
if (auto * ctx = v.context())
for (auto * elem : *ctx)
context.insert(NixStringContextElem::parse(elem, xpSettings));
context.insert(NixStringContextElem::parse(elem->view(), xpSettings));
}
std::string_view EvalState::forceString(

View file

@ -31,6 +31,7 @@ headers = [ config_pub_h ] + files(
'print.hh',
'repl-exit-status.hh',
'search-path.hh',
'static-string-data.hh',
'symbol-table.hh',
'value-to-json.hh',
'value-to-xml.hh',

View file

@ -3,6 +3,7 @@
#include <map>
#include <span>
#include <memory>
#include <vector>
#include <memory_resource>
#include <algorithm>
@ -11,6 +12,7 @@
#include "nix/expr/value.hh"
#include "nix/expr/symbol-table.hh"
#include "nix/expr/eval-error.hh"
#include "nix/expr/static-string-data.hh"
#include "nix/util/pos-idx.hh"
#include "nix/expr/counter.hh"
#include "nix/util/pos-table.hh"
@ -186,22 +188,18 @@ struct ExprString : Expr
* This is only for strings already allocated in our polymorphic allocator,
* or that live at least that long (e.g. c++ string literals)
*/
ExprString(const char * s)
ExprString(const StringData & s)
{
v.mkStringNoCopy(s);
};
ExprString(std::pmr::polymorphic_allocator<char> & alloc, std::string_view sv)
{
auto len = sv.length();
if (len == 0) {
v.mkStringNoCopy("");
if (sv.size() == 0) {
v.mkStringNoCopy(""_sds);
return;
}
char * s = alloc.allocate(len + 1);
sv.copy(s, len);
s[len] = '\0';
v.mkStringNoCopy(s);
v.mkStringNoCopy(StringData::make(*alloc.resource(), sv));
};
Value * maybeThunk(EvalState & state, Env & env) override;
@ -216,11 +214,7 @@ struct ExprPath : Expr
ExprPath(std::pmr::polymorphic_allocator<char> & alloc, ref<SourceAccessor> accessor, std::string_view sv)
: accessor(accessor)
{
auto len = sv.length();
char * s = alloc.allocate(len + 1);
sv.copy(s, len);
s[len] = '\0';
v.mkPath(&*accessor, s);
v.mkPath(&*accessor, StringData::make(*alloc.resource(), sv));
}
Value * maybeThunk(EvalState & state, Env & env) override;

View file

@ -4,6 +4,8 @@
#include <limits>
#include "nix/expr/eval.hh"
#include "nix/expr/value.hh"
#include "nix/expr/static-string-data.hh"
namespace nix {
@ -240,7 +242,7 @@ inline Expr *
ParserState::stripIndentation(const PosIdx pos, std::vector<std::pair<PosIdx, std::variant<Expr *, StringToken>>> && es)
{
if (es.empty())
return exprs.add<ExprString>("");
return exprs.add<ExprString>(""_sds);
/* Figure out the minimum indentation. Note that by design
whitespace-only final lines are not taken into account. (So
@ -332,7 +334,7 @@ ParserState::stripIndentation(const PosIdx pos, std::vector<std::pair<PosIdx, st
// If there is nothing at all, return the empty string directly.
// This also ensures that equivalent empty strings result in the same ast, which is helpful when testing formatters.
if (es2.size() == 0) {
auto * const result = exprs.add<ExprString>("");
auto * const result = exprs.add<ExprString>(""_sds);
return result;
}

View file

@ -0,0 +1,44 @@
#pragma once
///@file
#include "nix/expr/value.hh"
namespace nix {
template<size_t N>
struct StringData::Static
{
/**
* @note Must be first to make layout compatible with StringData.
*/
const size_t size = N - 1;
char data[N];
consteval Static(const char (&str)[N])
{
static_assert(N > 0);
if (str[size] != '\0')
throw;
std::copy_n(str, N, data);
}
operator const StringData &() const &
{
static_assert(sizeof(decltype(*this)) >= sizeof(StringData));
static_assert(alignof(decltype(*this)) == alignof(StringData));
/* NOTE: This cast is somewhat on the fence of what's legal in C++.
The question boils down to whether flexible array members are
layout compatible with fixed-size arrays. This is a gray area, since
FAMs are not standard anyway.
*/
return *reinterpret_cast<const StringData *>(this);
}
};
template<StringData::Static S>
const StringData & operator""_sds()
{
return S;
}
} // namespace nix

View file

@ -3,6 +3,7 @@
#include <memory_resource>
#include "nix/expr/value.hh"
#include "nix/expr/static-string-data.hh"
#include "nix/util/chunked-vector.hh"
#include "nix/util/error.hh"
@ -16,7 +17,6 @@ class SymbolValue : protected Value
friend class SymbolStr;
friend class SymbolTable;
uint32_t size_;
uint32_t idx;
SymbolValue() = default;
@ -24,7 +24,7 @@ class SymbolValue : protected Value
public:
operator std::string_view() const noexcept
{
return {c_str(), size_};
return string_view();
}
};
@ -96,13 +96,13 @@ class SymbolStr
SymbolValueStore & store;
std::string_view s;
std::size_t hash;
std::pmr::polymorphic_allocator<char> & alloc;
std::pmr::memory_resource & resource;
Key(SymbolValueStore & store, std::string_view s, std::pmr::polymorphic_allocator<char> & stringAlloc)
Key(SymbolValueStore & store, std::string_view s, std::pmr::memory_resource & stringMemory)
: store(store)
, s(s)
, hash(HashType{}(s))
, alloc(stringAlloc)
, resource(stringMemory)
{
}
};
@ -122,14 +122,10 @@ public:
// for multi-threaded implementations: lock store and allocator here
const auto & [v, idx] = key.store.add(SymbolValue{});
if (size == 0) {
v.mkStringNoCopy("", nullptr);
v.mkStringNoCopy(""_sds, nullptr);
} else {
auto s = key.alloc.allocate(size + 1);
memcpy(s, key.s.data(), size);
s[size] = '\0';
v.mkStringNoCopy(s, nullptr);
v.mkStringNoCopy(StringData::make(key.resource, key.s));
}
v.size_ = size;
v.idx = idx;
this->s = &v;
}
@ -139,6 +135,12 @@ public:
return *s == s2;
}
[[gnu::always_inline]]
const StringData & string_data() const noexcept
{
return s->string_data();
}
[[gnu::always_inline]]
const char * c_str() const noexcept
{
@ -155,13 +157,17 @@ public:
[[gnu::always_inline]]
bool empty() const noexcept
{
return s->size_ == 0;
auto * p = &s->string_data();
// Save a dereference in the sentinel value case
if (p == &""_sds)
return true;
return p->size() == 0;
}
[[gnu::always_inline]]
size_t size() const noexcept
{
return s->size_;
return s->string_data().size();
}
[[gnu::always_inline]]
@ -259,7 +265,6 @@ private:
* During its lifetime the monotonic buffer holds all strings and nodes, if the symbol set is node based.
*/
std::pmr::monotonic_buffer_resource buffer;
std::pmr::polymorphic_allocator<char> stringAlloc{&buffer};
SymbolStr::SymbolValueStore store{16};
/**
@ -282,7 +287,7 @@ public:
// Most symbols are looked up more than once, so we trade off insertion performance
// for lookup performance.
// FIXME: make this thread-safe.
return Symbol(*symbols.insert(SymbolStr::Key{store, s, stringAlloc}).first);
return Symbol(*symbols.insert(SymbolStr::Key{store, s, buffer}).first);
}
std::vector<SymbolStr> resolve(const std::vector<Symbol> & symbols) const

View file

@ -1,8 +1,14 @@
#pragma once
///@file
#include <bit>
#include <cassert>
#include <cstddef>
#include <cstring>
#include <memory>
#include <memory_resource>
#include <span>
#include <string_view>
#include <type_traits>
#include <concepts>
@ -186,6 +192,91 @@ public:
friend struct Value;
};
class StringData
{
public:
using size_type = std::size_t;
size_type size_;
char data_[];
/*
* This in particular ensures that we cannot have a `StringData`
* that we use by value, which is just what we want!
*
* Dynamically sized types aren't a thing in C++ and even flexible array
* members are a language extension and beyond the realm of standard C++.
* Technically, sizeof data_ member is 0 and the intended way to use flexible
* array members is to allocate sizeof(StrindData) + count * sizeof(char) bytes
* and the compiler will consider alignment restrictions for the FAM.
*
*/
StringData(StringData &&) = delete;
StringData & operator=(StringData &&) = delete;
StringData(const StringData &) = delete;
StringData & operator=(const StringData &) = delete;
~StringData() = default;
private:
StringData() = delete;
explicit StringData(size_type size)
: size_(size)
{
}
public:
/**
* Allocate StringData on the (possibly) GC-managed heap and copy
* the contents of s to it.
*/
static const StringData & make(std::string_view s);
/**
* Allocate StringData on the (possibly) GC-managed heap.
* @param size Length of the string (without the NUL terminator).
*/
static StringData & alloc(size_t size);
size_t size() const
{
return size_;
}
char * data() noexcept
{
return data_;
}
const char * data() const noexcept
{
return data_;
}
const char * c_str() const noexcept
{
return data_;
}
constexpr std::string_view view() const noexcept
{
return std::string_view(data_, size_);
}
template<size_t N>
struct Static;
static StringData & make(std::pmr::memory_resource & resource, std::string_view s)
{
auto & res =
*new (resource.allocate(sizeof(StringData) + s.size() + 1, alignof(StringData))) StringData(s.size());
std::memcpy(res.data_, s.data(), s.size());
res.data_[s.size()] = '\0';
return res;
}
};
namespace detail {
/**
@ -219,7 +310,7 @@ struct ValueBase
*/
struct StringWithContext
{
const char * c_str;
const StringData * str;
/**
* The type of the context itself.
@ -234,7 +325,7 @@ struct ValueBase
*/
struct Context
{
using value_type = const char *;
using value_type = const StringData *;
using size_type = std::size_t;
using iterator = const value_type *;
@ -285,7 +376,7 @@ struct ValueBase
struct Path
{
SourceAccessor * accessor;
const char * path;
const StringData * path;
};
struct Null
@ -646,13 +737,13 @@ protected:
void getStorage(StringWithContext & string) const noexcept
{
string.context = untagPointer<decltype(string.context)>(payload[0]);
string.c_str = std::bit_cast<const char *>(payload[1]);
string.str = std::bit_cast<const StringData *>(payload[1]);
}
void getStorage(Path & path) const noexcept
{
path.accessor = untagPointer<decltype(path.accessor)>(payload[0]);
path.path = std::bit_cast<const char *>(payload[1]);
path.path = std::bit_cast<const StringData *>(payload[1]);
}
void setStorage(NixInt integer) noexcept
@ -697,7 +788,7 @@ protected:
void setStorage(StringWithContext string) noexcept
{
setUntaggablePayload<pdString>(string.context, string.c_str);
setUntaggablePayload<pdString>(string.context, string.str);
}
void setStorage(Path path) noexcept
@ -1050,22 +1141,22 @@ public:
setStorage(b);
}
void mkStringNoCopy(const char * s, const Value::StringWithContext::Context * context = nullptr) noexcept
void mkStringNoCopy(const StringData & s, const Value::StringWithContext::Context * context = nullptr) noexcept
{
setStorage(StringWithContext{.c_str = s, .context = context});
setStorage(StringWithContext{.str = &s, .context = context});
}
void mkString(std::string_view s);
void mkString(std::string_view s, const NixStringContext & context);
void mkStringMove(const char * s, const NixStringContext & context);
void mkStringMove(const StringData & s, const NixStringContext & context);
void mkPath(const SourcePath & path);
inline void mkPath(SourceAccessor * accessor, const char * path) noexcept
inline void mkPath(SourceAccessor * accessor, const StringData & path) noexcept
{
setStorage(Path{.accessor = accessor, .path = path});
setStorage(Path{.accessor = accessor, .path = &path});
}
inline void mkNull() noexcept
@ -1163,17 +1254,23 @@ public:
SourcePath path() const
{
return SourcePath(ref(pathAccessor()->shared_from_this()), CanonPath(CanonPath::unchecked_t(), pathStr()));
return SourcePath(
ref(pathAccessor()->shared_from_this()), CanonPath(CanonPath::unchecked_t(), std::string(pathStrView())));
}
std::string_view string_view() const noexcept
const StringData & string_data() const noexcept
{
return std::string_view{getStorage<StringWithContext>().c_str};
return *getStorage<StringWithContext>().str;
}
const char * c_str() const noexcept
{
return getStorage<StringWithContext>().c_str;
return getStorage<StringWithContext>().str->data();
}
std::string_view string_view() const noexcept
{
return string_data().view();
}
const Value::StringWithContext::Context * context() const noexcept
@ -1233,12 +1330,12 @@ public:
const char * pathStr() const noexcept
{
return getStorage<Path>().path;
return getStorage<Path>().path->c_str();
}
std::string_view pathStrView() const noexcept
{
return std::string_view{getStorage<Path>().path};
return getStorage<Path>().path->view();
}
SourceAccessor * pathAccessor() const noexcept

View file

@ -5,6 +5,7 @@
#include "nix/expr/eval-settings.hh"
#include "nix/expr/gc-small-vector.hh"
#include "nix/expr/json-to-value.hh"
#include "nix/expr/static-string-data.hh"
#include "nix/store/globals.hh"
#include "nix/store/names.hh"
#include "nix/store/path-references.hh"
@ -487,34 +488,34 @@ static void prim_typeOf(EvalState & state, const PosIdx pos, Value ** args, Valu
state.forceValue(*args[0], pos);
switch (args[0]->type()) {
case nInt:
v.mkStringNoCopy("int");
v.mkStringNoCopy("int"_sds);
break;
case nBool:
v.mkStringNoCopy("bool");
v.mkStringNoCopy("bool"_sds);
break;
case nString:
v.mkStringNoCopy("string");
v.mkStringNoCopy("string"_sds);
break;
case nPath:
v.mkStringNoCopy("path");
v.mkStringNoCopy("path"_sds);
break;
case nNull:
v.mkStringNoCopy("null");
v.mkStringNoCopy("null"_sds);
break;
case nAttrs:
v.mkStringNoCopy("set");
v.mkStringNoCopy("set"_sds);
break;
case nList:
v.mkStringNoCopy("list");
v.mkStringNoCopy("list"_sds);
break;
case nFunction:
v.mkStringNoCopy("lambda");
v.mkStringNoCopy("lambda"_sds);
break;
case nExternal:
v.mkString(args[0]->external()->typeOf());
break;
case nFloat:
v.mkStringNoCopy("float");
v.mkStringNoCopy("float"_sds);
break;
case nThunk:
unreachable();
@ -2024,9 +2025,9 @@ static void prim_dirOf(EvalState & state, const PosIdx pos, Value ** args, Value
pos, *args[0], context, "while evaluating the first argument passed to 'builtins.dirOf'", false, false);
auto pos = path->rfind('/');
if (pos == path->npos)
v.mkStringMove(".", context);
v.mkStringMove("."_sds, context);
else if (pos == 0)
v.mkStringMove("/", context);
v.mkStringMove("/"_sds, context);
else
v.mkString(path->substr(0, pos), context);
}
@ -2309,10 +2310,10 @@ static const Value & fileTypeToString(EvalState & state, SourceAccessor::Type ty
static const Constants stringValues = []() {
Constants res;
res.regular.mkStringNoCopy("regular");
res.directory.mkStringNoCopy("directory");
res.symlink.mkStringNoCopy("symlink");
res.unknown.mkStringNoCopy("unknown");
res.regular.mkStringNoCopy("regular"_sds);
res.directory.mkStringNoCopy("directory"_sds);
res.symlink.mkStringNoCopy("symlink"_sds);
res.unknown.mkStringNoCopy("unknown"_sds);
return res;
}();
@ -4463,7 +4464,7 @@ static void prim_substring(EvalState & state, const PosIdx pos, Value ** args, V
if (len == 0) {
state.forceValue(*args[2], pos);
if (args[2]->type() == nString) {
v.mkStringNoCopy("", args[2]->context());
v.mkStringNoCopy(""_sds, args[2]->context());
return;
}
}

View file

@ -1,5 +1,6 @@
#include "nix/expr/primops.hh"
#include "nix/expr/eval-inline.hh"
#include "nix/expr/static-string-data.hh"
#include "expr-config-private.hh"
@ -136,7 +137,7 @@ static void prim_fromTOML(EvalState & state, const PosIdx pos, Value ** args, Va
normalizeDatetimeFormat(t);
#endif
auto attrs = state.buildBindings(2);
attrs.alloc("_type").mkStringNoCopy("timestamp");
attrs.alloc("_type").mkStringNoCopy("timestamp"_sds);
std::ostringstream s;
s << t;
auto str = s.view();

View file

@ -9,6 +9,7 @@
#include "nix/expr/eval-inline.hh"
#include "nix/store/profiles.hh"
#include "nix/expr/print-ambiguous.hh"
#include "nix/expr/static-string-data.hh"
#include <limits>
#include <sstream>
@ -56,7 +57,7 @@ bool createUserEnv(
auto attrs = state.buildBindings(7 + outputs.size());
attrs.alloc(state.s.type).mkStringNoCopy("derivation");
attrs.alloc(state.s.type).mkStringNoCopy("derivation"_sds);
attrs.alloc(state.s.name).mkString(i.queryName());
auto system = i.querySystem();
if (!system.empty())