mirror of
https://github.com/NixOS/nix.git
synced 2025-12-23 17:31:08 +01:00
Merge pull request #14846 from roberth/issue-14816
Fix empty input path segfault (#14816)
This commit is contained in:
commit
130a656330
10 changed files with 202 additions and 34 deletions
|
|
@ -116,7 +116,11 @@ MixFlakeOptions::MixFlakeOptions()
|
|||
.labels = {"input-path"},
|
||||
.handler = {[&](std::string s) {
|
||||
warn("'--update-input' is a deprecated alias for 'flake update' and will be removed in a future version.");
|
||||
lockFlags.inputUpdates.insert(flake::parseInputAttrPath(s));
|
||||
auto path = flake::NonEmptyInputAttrPath::parse(s);
|
||||
if (!path)
|
||||
throw UsageError(
|
||||
"--update-input was passed a zero-length input path, which would refer to the flake itself, not an input");
|
||||
lockFlags.inputUpdates.insert(*path);
|
||||
}},
|
||||
.completer = {[&](AddCompletions & completions, size_t, std::string_view prefix) {
|
||||
completeFlakeInputAttrPath(completions, getEvalState(), getFlakeRefsForCompletion(), prefix);
|
||||
|
|
@ -125,14 +129,18 @@ MixFlakeOptions::MixFlakeOptions()
|
|||
|
||||
addFlag({
|
||||
.longName = "override-input",
|
||||
.description = "Override a specific flake input (e.g. `dwarffs/nixpkgs`). This implies `--no-write-lock-file`.",
|
||||
.description =
|
||||
"Override a specific flake input (e.g. `dwarffs/nixpkgs`). The input path must not be empty. This implies `--no-write-lock-file`.",
|
||||
.category = category,
|
||||
.labels = {"input-path", "flake-url"},
|
||||
.handler = {[&](std::string inputAttrPath, std::string flakeRef) {
|
||||
lockFlags.writeLockFile = false;
|
||||
auto path = flake::NonEmptyInputAttrPath::parse(inputAttrPath);
|
||||
if (!path)
|
||||
throw UsageError(
|
||||
"--override-input was passed a zero-length input path, which would refer to the flake itself, not an input");
|
||||
lockFlags.inputOverrides.insert_or_assign(
|
||||
flake::parseInputAttrPath(inputAttrPath),
|
||||
parseFlakeRef(fetchSettings, flakeRef, absPath(getCommandBaseDir()).string(), true));
|
||||
std::move(*path), parseFlakeRef(fetchSettings, flakeRef, absPath(getCommandBaseDir()).string(), true));
|
||||
}},
|
||||
.completer = {[&](AddCompletions & completions, size_t n, std::string_view prefix) {
|
||||
if (n == 0) {
|
||||
|
|
|
|||
|
|
@ -162,8 +162,11 @@ nix_err nix_flake_lock_flags_add_input_override(
|
|||
{
|
||||
nix_clear_err(context);
|
||||
try {
|
||||
auto path = nix::flake::parseInputAttrPath(inputPath);
|
||||
flags->lockFlags->inputOverrides.emplace(path, *flakeRef->flakeRef);
|
||||
auto path = nix::flake::NonEmptyInputAttrPath::parse(inputPath);
|
||||
if (!path)
|
||||
throw nix::UsageError(
|
||||
"input override path cannot be zero-length; it would refer to the flake itself, not an input");
|
||||
flags->lockFlags->inputOverrides.emplace(std::move(*path), *flakeRef->flakeRef);
|
||||
if (flags->lockFlags->writeLockFile) {
|
||||
return nix_flake_lock_flags_set_mode_virtual(context, flags);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -160,8 +160,9 @@ nix_err nix_flake_lock_flags_set_mode_write_as_needed(nix_c_context * context, n
|
|||
* @brief Add input overrides to the lock flags
|
||||
* @param[out] context Optional, stores error information
|
||||
* @param[in] flags The flags to modify
|
||||
* @param[in] inputPath The input path to override
|
||||
* @param[in] inputPath The input path to override (must not be empty)
|
||||
* @param[in] flakeRef The flake reference to use as the override
|
||||
* @return NIX_ERR_NIX_ERROR if inputPath is empty
|
||||
*
|
||||
* This switches the `flags` to `nix_flake_lock_flags_set_mode_virtual` if not in mode
|
||||
* `nix_flake_lock_flags_set_mode_check`.
|
||||
|
|
|
|||
|
|
@ -384,4 +384,62 @@ TEST_F(nix_api_store_test, nix_api_load_flake_with_flags)
|
|||
nix_flake_settings_free(settings);
|
||||
}
|
||||
|
||||
TEST_F(nix_api_store_test, nix_api_flake_lock_flags_add_input_override_empty_path)
|
||||
{
|
||||
auto tmpDir = nix::createTempDir();
|
||||
nix::AutoDelete delTmpDir(tmpDir, true);
|
||||
|
||||
nix::writeFile(tmpDir / "flake.nix", R"(
|
||||
{
|
||||
outputs = { ... }: { };
|
||||
}
|
||||
)");
|
||||
|
||||
nix_libstore_init(ctx);
|
||||
assert_ctx_ok();
|
||||
|
||||
auto fetchSettings = nix_fetchers_settings_new(ctx);
|
||||
assert_ctx_ok();
|
||||
ASSERT_NE(nullptr, fetchSettings);
|
||||
|
||||
auto settings = nix_flake_settings_new(ctx);
|
||||
assert_ctx_ok();
|
||||
ASSERT_NE(nullptr, settings);
|
||||
|
||||
auto lockFlags = nix_flake_lock_flags_new(ctx, settings);
|
||||
assert_ctx_ok();
|
||||
ASSERT_NE(nullptr, lockFlags);
|
||||
|
||||
auto parseFlags = nix_flake_reference_parse_flags_new(ctx, settings);
|
||||
assert_ctx_ok();
|
||||
ASSERT_NE(nullptr, parseFlags);
|
||||
|
||||
auto r0 = nix_flake_reference_parse_flags_set_base_directory(
|
||||
ctx, parseFlags, tmpDir.string().c_str(), tmpDir.string().size());
|
||||
assert_ctx_ok();
|
||||
ASSERT_EQ(NIX_OK, r0);
|
||||
|
||||
nix_flake_reference * flakeReference = nullptr;
|
||||
std::string fragment;
|
||||
nix_flake_reference_and_fragment_from_string(
|
||||
ctx, fetchSettings, settings, parseFlags, ".", 1, &flakeReference, OBSERVE_STRING(fragment));
|
||||
assert_ctx_ok();
|
||||
ASSERT_NE(nullptr, flakeReference);
|
||||
|
||||
// Test that empty input path is rejected (issue #14816)
|
||||
auto r = nix_flake_lock_flags_add_input_override(ctx, lockFlags, "", flakeReference);
|
||||
ASSERT_EQ(NIX_ERR_NIX_ERROR, r);
|
||||
assert_ctx_err();
|
||||
|
||||
// Verify error message contains expected text
|
||||
const char * errMsg = nix_err_msg(nullptr, ctx, nullptr);
|
||||
ASSERT_NE(nullptr, errMsg);
|
||||
ASSERT_NE(std::string(errMsg).find("input override path cannot be zero-length"), std::string::npos);
|
||||
|
||||
nix_flake_reference_free(flakeReference);
|
||||
nix_flake_reference_parse_flags_free(parseFlags);
|
||||
nix_flake_lock_flags_free(lockFlags);
|
||||
nix_flake_settings_free(settings);
|
||||
}
|
||||
|
||||
} // namespace nixC
|
||||
|
|
|
|||
|
|
@ -451,9 +451,10 @@ lockFlake(const Settings & settings, EvalState & state, const FlakeRef & topRef,
|
|||
std::optional<InputAttrPath> parentInputAttrPath; // FIXME: rename to inputAttrPathPrefix?
|
||||
};
|
||||
|
||||
std::map<InputAttrPath, OverrideTarget> overrides;
|
||||
std::set<InputAttrPath> explicitCliOverrides;
|
||||
std::set<InputAttrPath> overridesUsed, updatesUsed;
|
||||
std::map<NonEmptyInputAttrPath, OverrideTarget> overrides;
|
||||
std::set<NonEmptyInputAttrPath> explicitCliOverrides;
|
||||
std::set<NonEmptyInputAttrPath> overridesUsed;
|
||||
std::set<InputAttrPath> updatesUsed;
|
||||
std::map<ref<Node>, SourcePath> nodePaths;
|
||||
|
||||
for (auto & i : lockFlags.inputOverrides) {
|
||||
|
|
@ -510,8 +511,7 @@ lockFlake(const Settings & settings, EvalState & state, const FlakeRef & topRef,
|
|||
auto addOverrides =
|
||||
[&](this const auto & addOverrides, const FlakeInput & input, const InputAttrPath & prefix) -> void {
|
||||
for (auto & [idOverride, inputOverride] : input.overrides) {
|
||||
auto inputAttrPath(prefix);
|
||||
inputAttrPath.push_back(idOverride);
|
||||
auto inputAttrPath = NonEmptyInputAttrPath::append(prefix, idOverride);
|
||||
if (inputOverride.ref || inputOverride.follows)
|
||||
overrides.emplace(
|
||||
inputAttrPath,
|
||||
|
|
@ -532,9 +532,8 @@ lockFlake(const Settings & settings, EvalState & state, const FlakeRef & topRef,
|
|||
/* Check whether this input has overrides for a
|
||||
non-existent input. */
|
||||
for (auto [inputAttrPath, inputOverride] : overrides) {
|
||||
auto inputAttrPath2(inputAttrPath);
|
||||
auto follow = inputAttrPath2.back();
|
||||
inputAttrPath2.pop_back();
|
||||
auto follow = inputAttrPath.inputName();
|
||||
auto inputAttrPath2 = inputAttrPath.parent();
|
||||
if (inputAttrPath2 == inputAttrPathPrefix && !flakeInputs.count(follow))
|
||||
warn(
|
||||
"input '%s' has an override for a non-existent input '%s'",
|
||||
|
|
@ -546,8 +545,8 @@ lockFlake(const Settings & settings, EvalState & state, const FlakeRef & topRef,
|
|||
necessary (i.e. if they're new or the flakeref changed
|
||||
from what's in the lock file). */
|
||||
for (auto & [id, input2] : flakeInputs) {
|
||||
auto inputAttrPath(inputAttrPathPrefix);
|
||||
inputAttrPath.push_back(id);
|
||||
auto nonEmptyInputAttrPath = NonEmptyInputAttrPath::append(inputAttrPathPrefix, id);
|
||||
auto inputAttrPath = nonEmptyInputAttrPath.get();
|
||||
auto inputAttrPathS = printInputAttrPath(inputAttrPath);
|
||||
debug("computing input '%s'", inputAttrPathS);
|
||||
|
||||
|
|
@ -555,11 +554,11 @@ lockFlake(const Settings & settings, EvalState & state, const FlakeRef & topRef,
|
|||
|
||||
/* Do we have an override for this input from one of the
|
||||
ancestors? */
|
||||
auto i = overrides.find(inputAttrPath);
|
||||
auto i = overrides.find(nonEmptyInputAttrPath);
|
||||
bool hasOverride = i != overrides.end();
|
||||
bool hasCliOverride = explicitCliOverrides.contains(inputAttrPath);
|
||||
bool hasCliOverride = explicitCliOverrides.contains(nonEmptyInputAttrPath);
|
||||
if (hasOverride)
|
||||
overridesUsed.insert(inputAttrPath);
|
||||
overridesUsed.insert(nonEmptyInputAttrPath);
|
||||
auto input = hasOverride ? i->second.input : input2;
|
||||
|
||||
/* Resolve relative 'path:' inputs relative to
|
||||
|
|
@ -618,7 +617,7 @@ lockFlake(const Settings & settings, EvalState & state, const FlakeRef & topRef,
|
|||
|
||||
updatesUsed.insert(inputAttrPath);
|
||||
|
||||
if (oldNode && !lockFlags.inputUpdates.count(inputAttrPath))
|
||||
if (oldNode && !lockFlags.inputUpdates.count(nonEmptyInputAttrPath))
|
||||
if (auto oldLock2 = get(oldNode->inputs, id))
|
||||
if (auto oldLock3 = std::get_if<0>(&*oldLock2))
|
||||
oldLock = *oldLock3;
|
||||
|
|
@ -637,10 +636,10 @@ lockFlake(const Settings & settings, EvalState & state, const FlakeRef & topRef,
|
|||
|
||||
/* If we have this input in updateInputs, then we
|
||||
must fetch the flake to update it. */
|
||||
auto lb = lockFlags.inputUpdates.lower_bound(inputAttrPath);
|
||||
auto lb = lockFlags.inputUpdates.lower_bound(nonEmptyInputAttrPath);
|
||||
|
||||
auto mustRefetch = lb != lockFlags.inputUpdates.end() && lb->size() > inputAttrPath.size()
|
||||
&& std::equal(inputAttrPath.begin(), inputAttrPath.end(), lb->begin());
|
||||
auto mustRefetch = lb != lockFlags.inputUpdates.end() && lb->get().size() > inputAttrPath.size()
|
||||
&& std::equal(inputAttrPath.begin(), inputAttrPath.end(), lb->get().begin());
|
||||
|
||||
FlakeInputs fakeInputs;
|
||||
|
||||
|
|
@ -662,8 +661,8 @@ lockFlake(const Settings & settings, EvalState & state, const FlakeRef & topRef,
|
|||
// It is possible that the flake has changed,
|
||||
// so we must confirm all the follows that are in the lock file are also in the
|
||||
// flake.
|
||||
auto overridePath(inputAttrPath);
|
||||
overridePath.push_back(i.first);
|
||||
auto overridePath =
|
||||
NonEmptyInputAttrPath::append(nonEmptyInputAttrPath, i.first);
|
||||
auto o = overrides.find(overridePath);
|
||||
// If the override disappeared, we have to refetch the flake,
|
||||
// since some of the inputs may not be present in the lock file.
|
||||
|
|
@ -717,7 +716,7 @@ lockFlake(const Settings & settings, EvalState & state, const FlakeRef & topRef,
|
|||
nuked the next time we update the lock
|
||||
file. That is, overrides are sticky unless you
|
||||
use --no-write-lock-file. */
|
||||
auto inputIsOverride = explicitCliOverrides.contains(inputAttrPath);
|
||||
auto inputIsOverride = explicitCliOverrides.contains(nonEmptyInputAttrPath);
|
||||
auto ref = (input2.ref && inputIsOverride) ? *input2.ref : *input.ref;
|
||||
|
||||
if (input.isFlake) {
|
||||
|
|
|
|||
|
|
@ -205,13 +205,13 @@ struct LockFlags
|
|||
/**
|
||||
* Flake inputs to be overridden.
|
||||
*/
|
||||
std::map<InputAttrPath, FlakeRef> inputOverrides;
|
||||
std::map<NonEmptyInputAttrPath, FlakeRef> inputOverrides;
|
||||
|
||||
/**
|
||||
* Flake inputs to be updated. This means that any existing lock
|
||||
* for those inputs will be ignored.
|
||||
*/
|
||||
std::set<InputAttrPath> inputUpdates;
|
||||
std::set<NonEmptyInputAttrPath> inputUpdates;
|
||||
};
|
||||
|
||||
LockedFlake
|
||||
|
|
|
|||
|
|
@ -14,6 +14,80 @@ namespace nix::flake {
|
|||
|
||||
typedef std::vector<FlakeId> InputAttrPath;
|
||||
|
||||
/**
|
||||
* A non-empty input attribute path.
|
||||
*
|
||||
* Input attribute paths identify inputs in a flake. An empty path would
|
||||
* refer to the flake itself rather than an input, which contradicts the
|
||||
* purpose of operations like override or update.
|
||||
*/
|
||||
class NonEmptyInputAttrPath
|
||||
{
|
||||
InputAttrPath path;
|
||||
|
||||
explicit NonEmptyInputAttrPath(InputAttrPath && p)
|
||||
: path(std::move(p))
|
||||
{
|
||||
assert(!path.empty());
|
||||
}
|
||||
|
||||
public:
|
||||
/**
|
||||
* Parse and validate a non-empty input attribute path.
|
||||
* Returns std::nullopt if the path is empty.
|
||||
*/
|
||||
static std::optional<NonEmptyInputAttrPath> parse(std::string_view s);
|
||||
|
||||
/**
|
||||
* Construct from an already-parsed path.
|
||||
* Returns std::nullopt if the path is empty.
|
||||
*/
|
||||
static std::optional<NonEmptyInputAttrPath> make(InputAttrPath path);
|
||||
|
||||
/**
|
||||
* Append an element to a path, creating a non-empty path.
|
||||
* This is always safe because adding an element guarantees non-emptiness.
|
||||
*/
|
||||
static NonEmptyInputAttrPath append(const InputAttrPath & prefix, const FlakeId & element)
|
||||
{
|
||||
InputAttrPath path = prefix;
|
||||
path.push_back(element);
|
||||
return NonEmptyInputAttrPath{std::move(path)};
|
||||
}
|
||||
|
||||
const InputAttrPath & get() const
|
||||
{
|
||||
return path;
|
||||
}
|
||||
|
||||
operator const InputAttrPath &() const
|
||||
{
|
||||
return path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the final component of the path (the input name).
|
||||
* For a path like "a/b/c", returns "c".
|
||||
*/
|
||||
const FlakeId & inputName() const
|
||||
{
|
||||
return path.back();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the parent path (all components except the last).
|
||||
* For a path like "a/b/c", returns "a/b".
|
||||
*/
|
||||
InputAttrPath parent() const
|
||||
{
|
||||
InputAttrPath result = path;
|
||||
result.pop_back();
|
||||
return result;
|
||||
}
|
||||
|
||||
auto operator<=>(const NonEmptyInputAttrPath & other) const = default;
|
||||
};
|
||||
|
||||
struct LockedNode;
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -316,6 +316,19 @@ InputAttrPath parseInputAttrPath(std::string_view s)
|
|||
return path;
|
||||
}
|
||||
|
||||
std::optional<NonEmptyInputAttrPath> NonEmptyInputAttrPath::parse(std::string_view s)
|
||||
{
|
||||
auto path = parseInputAttrPath(s);
|
||||
return make(std::move(path));
|
||||
}
|
||||
|
||||
std::optional<NonEmptyInputAttrPath> NonEmptyInputAttrPath::make(InputAttrPath path)
|
||||
{
|
||||
if (path.empty())
|
||||
return std::nullopt;
|
||||
return NonEmptyInputAttrPath{std::move(path)};
|
||||
}
|
||||
|
||||
std::map<InputAttrPath, Node::Edge> LockFile::getAllInputs() const
|
||||
{
|
||||
std::set<ref<Node>> done;
|
||||
|
|
|
|||
|
|
@ -90,9 +90,12 @@ public:
|
|||
.optional = true,
|
||||
.handler = {[&](std::vector<std::string> inputsToUpdate) {
|
||||
for (const auto & inputToUpdate : inputsToUpdate) {
|
||||
InputAttrPath inputAttrPath;
|
||||
std::optional<NonEmptyInputAttrPath> inputAttrPath;
|
||||
try {
|
||||
inputAttrPath = flake::parseInputAttrPath(inputToUpdate);
|
||||
inputAttrPath = flake::NonEmptyInputAttrPath::parse(inputToUpdate);
|
||||
if (!inputAttrPath)
|
||||
throw UsageError(
|
||||
"input path to be updated cannot be zero-length; it would refer to the flake itself, not an input");
|
||||
} catch (Error & e) {
|
||||
warn(
|
||||
"Invalid flake input '%s'. To update a specific flake, use 'nix flake update --flake %s' instead.",
|
||||
|
|
@ -100,11 +103,11 @@ public:
|
|||
inputToUpdate);
|
||||
throw e;
|
||||
}
|
||||
if (lockFlags.inputUpdates.contains(inputAttrPath))
|
||||
if (lockFlags.inputUpdates.contains(*inputAttrPath))
|
||||
warn(
|
||||
"Input '%s' was specified multiple times. You may have done this by accident.",
|
||||
printInputAttrPath(inputAttrPath));
|
||||
lockFlags.inputUpdates.insert(inputAttrPath);
|
||||
printInputAttrPath(*inputAttrPath));
|
||||
lockFlags.inputUpdates.insert(*inputAttrPath);
|
||||
}
|
||||
}},
|
||||
.completer = {[&](AddCompletions & completions, size_t, std::string_view prefix) {
|
||||
|
|
|
|||
|
|
@ -395,6 +395,12 @@ nix flake lock "$flake3Dir" --override-input flake2/flake1 flake1
|
|||
nix flake lock "$flake3Dir" --override-input flake2/flake1 flake1/master/"$hash1"
|
||||
[[ $(jq -r .nodes.flake1_2.locked.rev "$flake3Dir/flake.lock") =~ $hash1 ]]
|
||||
|
||||
# Test that --override-input with empty input path is rejected (issue #14816).
|
||||
expectStderr 1 nix flake lock "$flake3Dir" --override-input '' . | grepQuiet -- "--override-input was passed a zero-length input path, which would refer to the flake itself, not an input"
|
||||
|
||||
# Test that deprecated --update-input with empty input path is rejected.
|
||||
expectStderr 1 nix flake lock "$flake3Dir" --update-input '' | grepQuiet -- "--update-input was passed a zero-length input path, which would refer to the flake itself, not an input"
|
||||
|
||||
# Test --update-input.
|
||||
nix flake lock "$flake3Dir"
|
||||
[[ $(jq -r .nodes.flake1_2.locked.rev "$flake3Dir/flake.lock") = "$hash1" ]]
|
||||
|
|
@ -402,6 +408,9 @@ nix flake lock "$flake3Dir"
|
|||
nix flake update flake2/flake1 --flake "$flake3Dir"
|
||||
[[ $(jq -r .nodes.flake1_2.locked.rev "$flake3Dir/flake.lock") =~ $hash2 ]]
|
||||
|
||||
# Test that 'nix flake update' with empty input path is rejected.
|
||||
expectStderr 1 nix flake update '' --flake "$flake3Dir" | grepQuiet -- "input path to be updated cannot be zero-length; it would refer to the flake itself, not an input"
|
||||
|
||||
# Test updating multiple inputs.
|
||||
nix flake lock "$flake3Dir" --override-input flake1 flake1/master/"$hash1"
|
||||
nix flake lock "$flake3Dir" --override-input flake2/flake1 flake1/master/"$hash1"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue