From 078bbe61716133cefb88b702bf10140dd775e7a4 Mon Sep 17 00:00:00 2001 From: piegames Date: Thu, 30 Apr 2026 16:52:38 +0200 Subject: [PATCH] libexpr/eval: Fix off by one in maxCallDepth calculation This isn't really relevant for a setting of 10k, but now that we want to set it to 0 for Flakes it makes a difference as to whether or not one top-level function call is allowed or not. (It shouldn't be) Change-Id: I6bbf99826af0289c232b9d45d172a378b81abe4b --- lix/libexpr/eval.cc | 3 +- ...il-fail-maxCallDepth0-maxCallDepth.err.exp | 6 ++++ ...il-fail-maxCallDepth1-maxCallDepth.err.exp | 28 +++++++++++++++++++ .../eval-fail-infrec.err.exp | 2 +- .../eval-okay-maxCallDepth.out.exp | 1 + .../function-recursion/in-maxCallDepth.nix | 2 ++ .../lang/function-recursion/test.toml | 21 ++++++++++++++ 7 files changed, 61 insertions(+), 2 deletions(-) create mode 100644 tests/functional2/lang/function-recursion/evail-fail-maxCallDepth0-maxCallDepth.err.exp create mode 100644 tests/functional2/lang/function-recursion/evail-fail-maxCallDepth1-maxCallDepth.err.exp create mode 100644 tests/functional2/lang/function-recursion/eval-okay-maxCallDepth.out.exp create mode 100644 tests/functional2/lang/function-recursion/in-maxCallDepth.nix create mode 100644 tests/functional2/lang/function-recursion/test.toml diff --git a/lix/libexpr/eval.cc b/lix/libexpr/eval.cc index ab8e4f5ee..e9d24f941 100644 --- a/lix/libexpr/eval.cc +++ b/lix/libexpr/eval.cc @@ -1118,8 +1118,9 @@ Env & AttrsPattern::match( Value EvalState::callFunction(Value & fun, std::span args, const PosIdx pos) { - if (callDepth > evalSettings.maxCallDepth) + if (callDepth >= evalSettings.maxCallDepth) { ctx.errors.make("stack overflow; max-call-depth exceeded").atPos(pos).debugThrow(); + } MaintainCount _level(callDepth); auto trace = evalSettings.traceFunctionCalls diff --git a/tests/functional2/lang/function-recursion/evail-fail-maxCallDepth0-maxCallDepth.err.exp b/tests/functional2/lang/function-recursion/evail-fail-maxCallDepth0-maxCallDepth.err.exp new file mode 100644 index 000000000..f5e84bc23 --- /dev/null +++ b/tests/functional2/lang/function-recursion/evail-fail-maxCallDepth0-maxCallDepth.err.exp @@ -0,0 +1,6 @@ +error: stack overflow; max-call-depth exceeded + at /pwd/in.nix:2:22: + 1| # Exactly two function calls deep, so should fail with maxCallDepth < 2 + 2| let f = x: x + 1; in f (f 0) + | ^ + 3| diff --git a/tests/functional2/lang/function-recursion/evail-fail-maxCallDepth1-maxCallDepth.err.exp b/tests/functional2/lang/function-recursion/evail-fail-maxCallDepth1-maxCallDepth.err.exp new file mode 100644 index 000000000..ff266cda4 --- /dev/null +++ b/tests/functional2/lang/function-recursion/evail-fail-maxCallDepth1-maxCallDepth.err.exp @@ -0,0 +1,28 @@ +error: + … from call site + at /pwd/in.nix:2:22: + 1| # Exactly two function calls deep, so should fail with maxCallDepth < 2 + 2| let f = x: x + 1; in f (f 0) + | ^ + 3| + + … while calling 'f' + at /pwd/in.nix:2:9: + 1| # Exactly two function calls deep, so should fail with maxCallDepth < 2 + 2| let f = x: x + 1; in f (f 0) + | ^ + 3| + + … while evaluating x + at /pwd/in.nix:2:12: + 1| # Exactly two function calls deep, so should fail with maxCallDepth < 2 + 2| let f = x: x + 1; in f (f 0) + | ^ + 3| + + error: stack overflow; max-call-depth exceeded + at /pwd/in.nix:2:25: + 1| # Exactly two function calls deep, so should fail with maxCallDepth < 2 + 2| let f = x: x + 1; in f (f 0) + | ^ + 3| diff --git a/tests/functional2/lang/function-recursion/eval-fail-infrec.err.exp b/tests/functional2/lang/function-recursion/eval-fail-infrec.err.exp index ba5d38491..19a56a016 100644 --- a/tests/functional2/lang/function-recursion/eval-fail-infrec.err.exp +++ b/tests/functional2/lang/function-recursion/eval-fail-infrec.err.exp @@ -29,7 +29,7 @@ error: | ^ 2| - (19997 duplicate frames omitted) + (19995 duplicate frames omitted) error: stack overflow; max-call-depth exceeded at /pwd/in.nix:1:14: diff --git a/tests/functional2/lang/function-recursion/eval-okay-maxCallDepth.out.exp b/tests/functional2/lang/function-recursion/eval-okay-maxCallDepth.out.exp new file mode 100644 index 000000000..0cfbf0888 --- /dev/null +++ b/tests/functional2/lang/function-recursion/eval-okay-maxCallDepth.out.exp @@ -0,0 +1 @@ +2 diff --git a/tests/functional2/lang/function-recursion/in-maxCallDepth.nix b/tests/functional2/lang/function-recursion/in-maxCallDepth.nix new file mode 100644 index 000000000..64a8aa6ce --- /dev/null +++ b/tests/functional2/lang/function-recursion/in-maxCallDepth.nix @@ -0,0 +1,2 @@ +# Exactly two function calls deep, so should fail with maxCallDepth < 2 +let f = x: x + 1; in f (f 0) diff --git a/tests/functional2/lang/function-recursion/test.toml b/tests/functional2/lang/function-recursion/test.toml new file mode 100644 index 000000000..8f7d6dcfe --- /dev/null +++ b/tests/functional2/lang/function-recursion/test.toml @@ -0,0 +1,21 @@ +[[test]] +runner = "eval-fail" +in = ["in-infrec.nix", "in-mutrec.nix"] +matrix = true + +[[test]] +runner = "eval-okay" +in = "in-maxCallDepth.nix" +flags = ["--option", "max-call-depth", "2"] + +[[test]] +name = "evail-fail-maxCallDepth1" +runner = "eval-fail" +in = "in-maxCallDepth.nix" +flags = ["--option", "max-call-depth", "1"] + +[[test]] +name = "evail-fail-maxCallDepth0" +runner = "eval-fail" +in = "in-maxCallDepth.nix" +flags = ["--option", "max-call-depth", "0"]