Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Parse string context elements properly #7543

Merged
merged 2 commits into from
Jan 11, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/libcmd/installables.hh
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

#include "util.hh"
#include "path.hh"
#include "path-with-outputs.hh"
#include "outputs-spec.hh"
#include "derived-path.hh"
#include "eval.hh"
#include "store-api.hh"
Expand All @@ -20,7 +20,7 @@ namespace eval_cache { class EvalCache; class AttrCursor; }

struct App
{
std::vector<StorePathWithOutputs> context;
std::vector<DerivedPath> context;
Ericson2314 marked this conversation as resolved.
Show resolved Hide resolved
Path program;
// FIXME: add args, sandbox settings, metadata, ...
};
Expand Down
15 changes: 13 additions & 2 deletions src/libexpr/eval-cache.cc
Original file line number Diff line number Diff line change
Expand Up @@ -300,7 +300,7 @@ struct AttrDb
NixStringContext context;
if (!queryAttribute.isNull(3))
for (auto & s : tokenizeString<std::vector<std::string>>(queryAttribute.getStr(3), ";"))
context.push_back(decodeContext(cfg, s));
context.push_back(NixStringContextElem::parse(cfg, s));
return {{rowId, string_t{queryAttribute.getStr(2), context}}};
}
case AttrType::Bool:
Expand Down Expand Up @@ -592,7 +592,18 @@ string_t AttrCursor::getStringWithContext()
if (auto s = std::get_if<string_t>(&cachedValue->second)) {
bool valid = true;
for (auto & c : s->second) {
if (!root->state.store->isValidPath(c.first)) {
const StorePath & path = std::visit(overloaded {
[&](const NixStringContextElem::DrvDeep & d) -> const StorePath & {
return d.drvPath;
},
[&](const NixStringContextElem::Built & b) -> const StorePath & {
return b.drvPath;
},
[&](const NixStringContextElem::Opaque & o) -> const StorePath & {
return o.path;
},
}, c.raw());
if (!root->state.store->isValidPath(path)) {
valid = false;
break;
}
Expand Down
23 changes: 1 addition & 22 deletions src/libexpr/eval.cc
Original file line number Diff line number Diff line change
Expand Up @@ -2068,27 +2068,6 @@ std::string_view EvalState::forceString(Value & v, const PosIdx pos, std::string
}


/* Decode a context string ‘!<name>!<path>’ into a pair <path,
name>. */
NixStringContextElem decodeContext(const Store & store, std::string_view s)
{
if (s.at(0) == '!') {
size_t index = s.find("!", 1);
return {
store.parseStorePath(s.substr(index + 1)),
std::string(s.substr(1, index - 1)),
};
} else
return {
store.parseStorePath(
s.at(0) == '/'
? s
: s.substr(1)),
"",
};
}


void copyContext(const Value & v, PathSet & context)
{
if (v.string.context)
Expand All @@ -2103,7 +2082,7 @@ NixStringContext Value::getContext(const Store & store)
assert(internalType == tString);
if (string.context)
for (const char * * p = string.context; *p; ++p)
res.push_back(decodeContext(store, *p));
res.push_back(NixStringContextElem::parse(store, *p));
return res;
}

Expand Down
4 changes: 0 additions & 4 deletions src/libexpr/eval.hh
Original file line number Diff line number Diff line change
Expand Up @@ -551,10 +551,6 @@ struct DebugTraceStacker {
std::string_view showType(ValueType type);
std::string showType(const Value & v);

/* Decode a context string ‘!<name>!<path>’ into a pair <path,
name>. */
NixStringContextElem decodeContext(const Store & store, std::string_view s);

/* If `path' refers to a directory, then append "/default.nix". */
Path resolveExprPath(Path path);

Expand Down
2 changes: 1 addition & 1 deletion src/libexpr/flake/flakeref.hh
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
#include "types.hh"
#include "hash.hh"
#include "fetchers.hh"
#include "path-with-outputs.hh"
#include "outputs-spec.hh"

#include <variant>

Expand Down
3 changes: 3 additions & 0 deletions src/libexpr/local.mk
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ libexpr_DIR := $(d)

libexpr_SOURCES := \
$(wildcard $(d)/*.cc) \
$(wildcard $(d)/value/*.cc) \
$(wildcard $(d)/primops/*.cc) \
$(wildcard $(d)/flake/*.cc) \
$(d)/lexer-tab.cc \
Expand Down Expand Up @@ -37,6 +38,8 @@ clean-files += $(d)/parser-tab.cc $(d)/parser-tab.hh $(d)/lexer-tab.cc $(d)/lexe

$(eval $(call install-file-in, $(d)/nix-expr.pc, $(libdir)/pkgconfig, 0644))

$(foreach i, $(wildcard src/libexpr/value/*.hh), \
$(eval $(call install-file-in, $(i), $(includedir)/nix/value, 0644)))
$(foreach i, $(wildcard src/libexpr/flake/*.hh), \
$(eval $(call install-file-in, $(i), $(includedir)/nix/flake, 0644)))

Expand Down
90 changes: 51 additions & 39 deletions src/libexpr/primops.cc
Original file line number Diff line number Diff line change
Expand Up @@ -43,16 +43,32 @@ StringMap EvalState::realiseContext(const PathSet & context)
std::vector<DerivedPath::Built> drvs;
StringMap res;

for (auto & i : context) {
auto [ctx, outputName] = decodeContext(*store, i);
auto ctxS = store->printStorePath(ctx);
if (!store->isValidPath(ctx))
debugThrowLastTrace(InvalidPathError(store->printStorePath(ctx)));
if (!outputName.empty() && ctx.isDerivation()) {
drvs.push_back({ctx, {outputName}});
} else {
res.insert_or_assign(ctxS, ctxS);
}
for (auto & c_ : context) {
auto ensureValid = [&](const StorePath & p) {
if (!store->isValidPath(p))
debugThrowLastTrace(InvalidPathError(store->printStorePath(p)));
};
auto c = NixStringContextElem::parse(*store, c_);
std::visit(overloaded {
[&](const NixStringContextElem::Built & b) {
drvs.push_back(DerivedPath::Built {
.drvPath = b.drvPath,
.outputs = std::set { b.output },
});
ensureValid(b.drvPath);
},
[&](const NixStringContextElem::Opaque & o) {
auto ctxS = store->printStorePath(o.path);
res.insert_or_assign(ctxS, ctxS);
ensureValid(o.path);
},
[&](const NixStringContextElem::DrvDeep & d) {
/* Treat same as Opaque */
auto ctxS = store->printStorePath(d.drvPath);
res.insert_or_assign(ctxS, ctxS);
ensureValid(d.drvPath);
},
}, c.raw());
}

if (drvs.empty()) return {};
Expand Down Expand Up @@ -1179,35 +1195,31 @@ static void prim_derivationStrict(EvalState & state, const PosIdx pos, Value * *
/* Everything in the context of the strings in the derivation
attributes should be added as dependencies of the resulting
derivation. */
for (auto & path : context) {

/* Paths marked with `=' denote that the path of a derivation
is explicitly passed to the builder. Since that allows the
builder to gain access to every path in the dependency
graph of the derivation (including all outputs), all paths
in the graph must be added to this derivation's list of
inputs to ensure that they are available when the builder
runs. */
if (path.at(0) == '=') {
/* !!! This doesn't work if readOnlyMode is set. */
StorePathSet refs;
state.store->computeFSClosure(state.store->parseStorePath(std::string_view(path).substr(1)), refs);
for (auto & j : refs) {
drv.inputSrcs.insert(j);
if (j.isDerivation())
drv.inputDrvs[j] = state.store->readDerivation(j).outputNames();
}
}

/* Handle derivation outputs of the form ‘!<name>!<path>’. */
else if (path.at(0) == '!') {
auto ctx = decodeContext(*state.store, path);
drv.inputDrvs[ctx.first].insert(ctx.second);
}

/* Otherwise it's a source file. */
else
drv.inputSrcs.insert(state.store->parseStorePath(path));
for (auto & c_ : context) {
auto c = NixStringContextElem::parse(*state.store, c_);
std::visit(overloaded {
/* Since this allows the builder to gain access to every
path in the dependency graph of the derivation (including
all outputs), all paths in the graph must be added to
this derivation's list of inputs to ensure that they are
available when the builder runs. */
[&](const NixStringContextElem::DrvDeep & d) {
/* !!! This doesn't work if readOnlyMode is set. */
StorePathSet refs;
state.store->computeFSClosure(d.drvPath, refs);
for (auto & j : refs) {
drv.inputSrcs.insert(j);
if (j.isDerivation())
drv.inputDrvs[j] = state.store->readDerivation(j).outputNames();
}
},
[&](const NixStringContextElem::Built & b) {
drv.inputDrvs[b.drvPath].insert(b.output);
},
[&](const NixStringContextElem::Opaque & o) {
drv.inputSrcs.insert(o.path);
},
}, c.raw());
}

/* Do we have all required attributes? */
Expand Down
51 changes: 23 additions & 28 deletions src/libexpr/primops/context.cc
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,15 @@ static void prim_unsafeDiscardOutputDependency(EvalState & state, const PosIdx p
auto s = state.coerceToString(pos, *args[0], context, "while evaluating the argument passed to builtins.unsafeDiscardOutputDependency");

PathSet context2;
for (auto & p : context)
context2.insert(p.at(0) == '=' ? std::string(p, 1) : p);
for (auto && p : context) {
Ericson2314 marked this conversation as resolved.
Show resolved Hide resolved
auto c = NixStringContextElem::parse(*state.store, p);
if (auto * ptr = std::get_if<NixStringContextElem::DrvDeep>(&c)) {
context2.emplace(state.store->printStorePath(ptr->drvPath));
} else {
/* Can reuse original item */
context2.emplace(std::move(p));
Ericson2314 marked this conversation as resolved.
Show resolved Hide resolved
}
}

v.mkString(*s, context2);
}
Expand Down Expand Up @@ -74,34 +81,22 @@ static void prim_getContext(EvalState & state, const PosIdx pos, Value * * args,
};
PathSet context;
state.forceString(*args[0], context, pos, "while evaluating the argument passed to builtins.getContext");
auto contextInfos = std::map<Path, ContextInfo>();
auto contextInfos = std::map<StorePath, ContextInfo>();
for (const auto & p : context) {
Path drv;
std::string output;
const Path * path = &p;
if (p.at(0) == '=') {
drv = std::string(p, 1);
path = &drv;
} else if (p.at(0) == '!') {
NixStringContextElem ctx = decodeContext(*state.store, p);
drv = state.store->printStorePath(ctx.first);
output = ctx.second;
path = &drv;
}
auto isPath = drv.empty();
auto isAllOutputs = (!drv.empty()) && output.empty();

auto iter = contextInfos.find(*path);
if (iter == contextInfos.end()) {
contextInfos.emplace(*path, ContextInfo{isPath, isAllOutputs, output.empty() ? Strings{} : Strings{std::move(output)}});
} else {
if (isPath)
iter->second.path = true;
else if (isAllOutputs)
iter->second.allOutputs = true;
else
iter->second.outputs.emplace_back(std::move(output));
}
NixStringContextElem ctx = NixStringContextElem::parse(*state.store, p);
std::visit(overloaded {
[&](NixStringContextElem::DrvDeep & d) {
contextInfos[d.drvPath].allOutputs = true;
},
[&](NixStringContextElem::Built & b) {
contextInfos[b.drvPath].outputs.emplace_back(std::move(output));
},
[&](NixStringContextElem::Opaque & o) {
contextInfos[o.path].path = true;
},
}, ctx.raw());
}

auto attrs = state.buildBindings(contextInfos.size());
Expand All @@ -120,7 +115,7 @@ static void prim_getContext(EvalState & state, const PosIdx pos, Value * * args,
for (const auto & [i, output] : enumerate(info.second.outputs))
(outputsVal.listElems()[i] = state.allocValue())->mkString(output);
}
attrs.alloc(info.first).mkAttrs(infoAttrs);
attrs.alloc(state.store->printStorePath(info.first)).mkAttrs(infoAttrs);
}

v.mkAttrs(attrs);
Expand Down
4 changes: 3 additions & 1 deletion src/libexpr/tests/local.mk
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@ libexpr-tests_DIR := $(d)

libexpr-tests_INSTALL_DIR :=

libexpr-tests_SOURCES := $(wildcard $(d)/*.cc)
libexpr-tests_SOURCES := \
$(wildcard $(d)/*.cc) \
$(wildcard $(d)/value/*.cc)

libexpr-tests_CXXFLAGS += -I src/libexpr -I src/libutil -I src/libstore -I src/libexpr/tests

Expand Down
72 changes: 72 additions & 0 deletions src/libexpr/tests/value/context.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
#include "value/context.hh"

#include "libexprtests.hh"

namespace nix {

// Testing of trivial expressions
struct NixStringContextElemTest : public LibExprTest {
const Store & store() const {
return *LibExprTest::store;
}
};

TEST_F(NixStringContextElemTest, empty_invalid) {
EXPECT_THROW(
NixStringContextElem::parse(store(), ""),
BadNixStringContextElem);
}

TEST_F(NixStringContextElemTest, single_bang_invalid) {
EXPECT_THROW(
NixStringContextElem::parse(store(), "!"),
BadNixStringContextElem);
}

TEST_F(NixStringContextElemTest, double_bang_invalid) {
EXPECT_THROW(
NixStringContextElem::parse(store(), "!!/"),
BadStorePath);
}

TEST_F(NixStringContextElemTest, eq_slash_invalid) {
EXPECT_THROW(
NixStringContextElem::parse(store(), "=/"),
BadStorePath);
}

TEST_F(NixStringContextElemTest, slash_invalid) {
EXPECT_THROW(
NixStringContextElem::parse(store(), "/"),
BadStorePath);
}

TEST_F(NixStringContextElemTest, opaque) {
std::string_view opaque = "/nix/store/g1w7hy3qg1w7hy3qg1w7hy3qg1w7hy3q-x";
auto elem = NixStringContextElem::parse(store(), opaque);
auto * p = std::get_if<NixStringContextElem::Opaque>(&elem);
ASSERT_TRUE(p);
ASSERT_EQ(p->path, store().parseStorePath(opaque));
ASSERT_EQ(elem.to_string(store()), opaque);
}

TEST_F(NixStringContextElemTest, drvDeep) {
std::string_view drvDeep = "=/nix/store/g1w7hy3qg1w7hy3qg1w7hy3qg1w7hy3q-x.drv";
auto elem = NixStringContextElem::parse(store(), drvDeep);
auto * p = std::get_if<NixStringContextElem::DrvDeep>(&elem);
ASSERT_TRUE(p);
ASSERT_EQ(p->drvPath, store().parseStorePath(drvDeep.substr(1)));
ASSERT_EQ(elem.to_string(store()), drvDeep);
}

TEST_F(NixStringContextElemTest, built) {
std::string_view built = "!foo!/nix/store/g1w7hy3qg1w7hy3qg1w7hy3qg1w7hy3q-x.drv";
auto elem = NixStringContextElem::parse(store(), built);
auto * p = std::get_if<NixStringContextElem::Built>(&elem);
ASSERT_TRUE(p);
ASSERT_EQ(p->output, "foo");
ASSERT_EQ(p->drvPath, store().parseStorePath(built.substr(5)));
ASSERT_EQ(elem.to_string(store()), built);
}
Comment on lines +14 to +70
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shiny new tests! :)


}
Loading