diff --git a/lix/libexpr/eval-expr.cc b/lix/libexpr/eval-expr.cc index 3400ce689..5676499e0 100644 --- a/lix/libexpr/eval-expr.cc +++ b/lix/libexpr/eval-expr.cc @@ -158,13 +158,22 @@ void ExprSet::eval(EvalState & state, Env & env, Value & v) /* Dynamic attrs apply *after* rec and __overrides. */ for (auto & i : dynamicAttrs) { + /* Before evaluating dynamic attrs, we blackhole the output attrset and only restore it after the operation. + * This is to avoid exposing the partially constructed set as a value, see + * http://github.com/NixOS/nix/issues/7012. Any accesses to the output attrset will thus infrec. + */ + Value vBackup = v; Value nameVal; - i.nameExpr->eval(state, *dynamicEnv, nameVal); - state.forceValue(nameVal, i.pos); - if (nameVal.type() == nNull) { - continue; + { + KJ_DEFER(v = vBackup); + v = Value{NewValueAs::blackhole}; + i.nameExpr->eval(state, *dynamicEnv, nameVal); + state.forceValue(nameVal, i.pos); + if (nameVal.type() == nNull) { + continue; + } + state.forceStringNoCtx(nameVal, i.pos, "while evaluating the name of a dynamic attribute"); } - state.forceStringNoCtx(nameVal, i.pos, "while evaluating the name of a dynamic attribute"); auto nameSym = state.ctx.symbols.create(nameVal.str()); auto j = v.attrs()->get(nameSym); if (j) { diff --git a/tests/functional2/lang/attrs-dynamic/eval-fail-incomplete-state.err.exp b/tests/functional2/lang/attrs-dynamic/eval-fail-incomplete-state.err.exp new file mode 100644 index 000000000..795422216 --- /dev/null +++ b/tests/functional2/lang/attrs-dynamic/eval-fail-incomplete-state.err.exp @@ -0,0 +1,20 @@ +error: + … while evaluating a + at /pwd/in.nix:1:84: + 1| let b = builtins.toJSON a; a = { foo = "bar"; "${builtins.seq b "baz"}" = b; }; in a + | ^ + 2| + + … while calling the 'seq' builtin + at /pwd/in.nix:1:50: + 1| let b = builtins.toJSON a; a = { foo = "bar"; "${builtins.seq b "baz"}" = b; }; in a + | ^ + 2| + + … while calling the 'toJSON' builtin + at /pwd/in.nix:1:9: + 1| let b = builtins.toJSON a; a = { foo = "bar"; "${builtins.seq b "baz"}" = b; }; in a + | ^ + 2| + + error: infinite recursion encountered diff --git a/tests/functional2/lang/attrs-dynamic/eval-fail-infrec.err.exp b/tests/functional2/lang/attrs-dynamic/eval-fail-infrec.err.exp new file mode 100644 index 000000000..7bda12454 --- /dev/null +++ b/tests/functional2/lang/attrs-dynamic/eval-fail-infrec.err.exp @@ -0,0 +1,50 @@ +error: + … from call site + at /pwd/in.nix:4:11: + 3| b = { ${lenName a} = null; }; + 4| a = { ${lenName b} = null; }; + | ^ + 5| + + … while calling 'lenName' + at /pwd/in.nix:2:13: + 1| let + 2| lenName = x: toString (builtins.length (builtins.attrNames x)); + | ^ + 3| b = { ${lenName a} = null; }; + + … while calling the 'toString' builtin + at /pwd/in.nix:2:16: + 1| let + 2| lenName = x: toString (builtins.length (builtins.attrNames x)); + | ^ + 3| b = { ${lenName a} = null; }; + + … while calling the 'length' builtin + at /pwd/in.nix:2:26: + 1| let + 2| lenName = x: toString (builtins.length (builtins.attrNames x)); + | ^ + 3| b = { ${lenName a} = null; }; + + … while evaluating the first argument passed to builtins.length + + … while calling the 'attrNames' builtin + at /pwd/in.nix:2:43: + 1| let + 2| lenName = x: toString (builtins.length (builtins.attrNames x)); + | ^ + 3| b = { ${lenName a} = null; }; + + … while evaluating the argument passed to builtins.attrNames + + … from call site + at /pwd/in.nix:3:11: + 2| lenName = x: toString (builtins.length (builtins.attrNames x)); + 3| b = { ${lenName a} = null; }; + | ^ + 4| a = { ${lenName b} = null; }; + + (6 duplicate frames omitted) + + error: infinite recursion encountered diff --git a/tests/functional2/lang/attrs-dynamic/in-incomplete-state.nix b/tests/functional2/lang/attrs-dynamic/in-incomplete-state.nix new file mode 100644 index 000000000..b82d8aaf6 --- /dev/null +++ b/tests/functional2/lang/attrs-dynamic/in-incomplete-state.nix @@ -0,0 +1 @@ +let b = builtins.toJSON a; a = { foo = "bar"; "${builtins.seq b "baz"}" = b; }; in a diff --git a/tests/functional2/lang/attrs-dynamic/in-infrec.nix b/tests/functional2/lang/attrs-dynamic/in-infrec.nix new file mode 100644 index 000000000..dc47f3b14 --- /dev/null +++ b/tests/functional2/lang/attrs-dynamic/in-infrec.nix @@ -0,0 +1,8 @@ +let + lenName = x: toString (builtins.length (builtins.attrNames x)); + b = { ${lenName a} = null; }; + a = { ${lenName b} = null; }; + +in + +[ a b ]