diff --git a/src/libexpr/include/nix/expr/print-ambiguous.hh b/src/libexpr/include/nix/expr/print-ambiguous.hh index c0d811d4b..7e44a6b66 100644 --- a/src/libexpr/include/nix/expr/print-ambiguous.hh +++ b/src/libexpr/include/nix/expr/print-ambiguous.hh @@ -5,6 +5,8 @@ namespace nix { +class EvalState; + /** * Print a value in the deprecated format used by `nix-instantiate --eval` and * `nix-env` (for manifests). @@ -15,7 +17,6 @@ namespace nix { * * See: https://github.com/NixOS/nix/issues/9730 */ -void printAmbiguous( - Value & v, const SymbolTable & symbols, std::ostream & str, std::set * seen, int depth); +void printAmbiguous(EvalState & state, Value & v, std::ostream & str, std::set * seen, size_t depth = 0); } // namespace nix diff --git a/src/libexpr/print-ambiguous.cc b/src/libexpr/print-ambiguous.cc index 8b80e2a66..cb1a1ef2d 100644 --- a/src/libexpr/print-ambiguous.cc +++ b/src/libexpr/print-ambiguous.cc @@ -2,19 +2,17 @@ #include "nix/expr/print.hh" #include "nix/util/signals.hh" #include "nix/expr/eval.hh" +#include "nix/expr/eval-error.hh" namespace nix { // See: https://github.com/NixOS/nix/issues/9730 -void printAmbiguous( - Value & v, const SymbolTable & symbols, std::ostream & str, std::set * seen, int depth) +void printAmbiguous(EvalState & state, Value & v, std::ostream & str, std::set * seen, size_t depth) { checkInterrupt(); - if (depth <= 0) { - str << "«too deep»"; - return; - } + if (depth > state.settings.maxCallDepth) + state.error().atPos(v.determinePos(noPos)).debugThrow(); switch (v.type()) { case nInt: str << v.integer(); @@ -36,9 +34,9 @@ void printAmbiguous( str << "«repeated»"; else { str << "{ "; - for (auto & i : v.attrs()->lexicographicOrder(symbols)) { - str << symbols[i->name] << " = "; - printAmbiguous(*i->value, symbols, str, seen, depth - 1); + for (auto & i : v.attrs()->lexicographicOrder(state.symbols)) { + str << state.symbols[i->name] << " = "; + printAmbiguous(state, *i->value, str, seen, depth + 1); str << "; "; } str << "}"; @@ -54,7 +52,7 @@ void printAmbiguous( str << "[ "; for (auto v2 : v.listView()) { if (v2) - printAmbiguous(*v2, symbols, str, seen, depth - 1); + printAmbiguous(state, *v2, str, seen, depth + 1); else str << "(nullptr)"; str << " "; diff --git a/src/nix/nix-env/user-env.cc b/src/nix/nix-env/user-env.cc index 21fdf25bc..6275e37a2 100644 --- a/src/nix/nix-env/user-env.cc +++ b/src/nix/nix-env/user-env.cc @@ -108,7 +108,7 @@ bool createUserEnv( environment. */ auto manifestFile = ({ std::ostringstream str; - printAmbiguous(manifest, state.symbols, str, nullptr, std::numeric_limits::max()); + printAmbiguous(state, manifest, str, nullptr); StringSource source{str.view()}; state.store->addToStoreFromDump( source, diff --git a/src/nix/nix-instantiate/nix-instantiate.cc b/src/nix/nix-instantiate/nix-instantiate.cc index 3d5c3e26a..e6d7c9186 100644 --- a/src/nix/nix-instantiate/nix-instantiate.cc +++ b/src/nix/nix-instantiate/nix-instantiate.cc @@ -68,7 +68,7 @@ void processExpr( if (strict) state.forceValueDeep(vRes); std::set seen; - printAmbiguous(vRes, state.symbols, std::cout, &seen, std::numeric_limits::max()); + printAmbiguous(state, vRes, std::cout, &seen); std::cout << std::endl; } } else { diff --git a/tests/functional/simple.sh b/tests/functional/simple.sh index 52304b509..f6507d741 100755 --- a/tests/functional/simple.sh +++ b/tests/functional/simple.sh @@ -46,3 +46,13 @@ expectStderr 1 nix-instantiate --expr 'let x = { recurseForDerivations = true; m echo 'let f = n: { type = "derivation"; name = "test"; system = "x86_64-linux"; meta.nested = f (n + 1); }; in { pkg = f 0; }' > "$TEST_ROOT/deep-meta.nix" expectStderr 1 nix-env -qa -f "$TEST_ROOT/deep-meta.nix" --json --meta \ | grepQuiet "stack overflow; max-call-depth exceeded" + +# Test that nix-instantiate --eval on a pre-forced deep structure (built with +# foldl' to avoid thunks) produces a controlled stack overflow error rather than +# a segfault when printAmbiguous traverses the structure. +# Note: Without the fix, this test may pass if the system stack is large enough. +# The fix ensures we get a controlled error at max-call-depth (default 10000) +# rather than relying on the system stack limit. +# shellcheck disable=SC2016 +expectStderr 1 nix-instantiate --eval --expr 'builtins.foldl'\'' (acc: _: { inner = acc; }) null (builtins.genList (x: x) 20000)' \ + | grepQuiet "stack overflow; max-call-depth exceeded"