lib/generators: withRecursion: don't break attr-sets with special attrs

Closes #168327

The issue reported there can be demonstrated with the following
expression:

    → nix-instantiate --eval -E "with import ./. {}; pkgs.lib.options.showDefs [ { file = \"foo\"; value = pkgs.rust.packages.stable.buildRustPackages; } ]"
    error: attempt to call something which is not a function but a string

           at /home/ma27/Projects/nixpkgs/lib/trivial.nix:442:35:

              441|   isFunction = f: builtins.isFunction f ||
              442|     (f ? __functor && isFunction (f.__functor f));
                 |                                   ^
              443|

Basically, if a `__functor` is in an attribute-set at depth-limit,
`__functor` will be set to `"<unevaluated>"`. This however breaks
`lib.isFunction` which checks for a `__functor` by invoking `__functor`
with `f` itself.

The same issue - "magic" attributes being shadowed by `withRecursion` -
also applies to others such as
`__pretty`/`__functionArgs`/`__toString`.

Since these attributes have a low-risk of causing a stack overflow
(because these are flat attr-sets or even functions), ignoring them in
`withRecursion` seems like a valid solution.
This commit is contained in:
Maximilian Bosch 2022-04-12 12:26:25 +02:00
parent dbe672e4a7
commit 7203788068
No known key found for this signature in database
GPG Key ID: 091DBF4D1FC46B8E
2 changed files with 26 additions and 1 deletions

View File

@ -251,6 +251,16 @@ rec {
}: }:
assert builtins.isInt depthLimit; assert builtins.isInt depthLimit;
let let
specialAttrs = [
"__functor"
"__functionArgs"
"__toString"
"__pretty"
];
stepIntoAttr = evalNext: name:
if builtins.elem name specialAttrs
then id
else evalNext;
transform = depth: transform = depth:
if depthLimit != null && depth > depthLimit then if depthLimit != null && depth > depthLimit then
if throwOnDepthLimit if throwOnDepthLimit
@ -261,7 +271,7 @@ rec {
let let
evalNext = x: mapAny (depth + 1) (transform (depth + 1) x); evalNext = x: mapAny (depth + 1) (transform (depth + 1) x);
in in
if isAttrs v then mapAttrs (const evalNext) v if isAttrs v then mapAttrs (stepIntoAttr evalNext) v
else if isList v then map evalNext v else if isList v then map evalNext v
else transform (depth + 1) v; else transform (depth + 1) v;
in in

View File

@ -613,6 +613,21 @@ runTests {
expected = false; expected = false;
}; };
testWithRecursionDealsWithFunctors =
let
functor = {
__functor = self: { a, b, }: null;
};
a = {
value = "1234";
b = functor;
c.d = functor;
};
in {
expr = generators.toPretty { } (generators.withRecursion { depthLimit = 1; throwOnDepthLimit = false; } a);
expected = "{\n b = <function, args: {a, b}>;\n c = {\n d = \"<unevaluated>\";\n };\n value = \"<unevaluated>\";\n}";
};
testToPrettyMultiline = { testToPrettyMultiline = {
expr = mapAttrs (const (generators.toPretty { })) rec { expr = mapAttrs (const (generators.toPretty { })) rec {
list = [ 3 4 [ false ] ]; list = [ 3 4 [ false ] ];