libexpr: Deprecate overriding __sub and the like

It was never intended to be a feature to be used, and moreover it is
inconsistent: One cannot override `+`, and overriding `__lessThan` won't
affect the builtins which do comparisons.

Change-Id: Iaba54a05aa4c2eb37cdb3dc0d731fcee5a86deba
This commit is contained in:
piegames 2024-11-18 12:56:46 +01:00
parent 66f6dbda32
commit 81d5f0a7d9
16 changed files with 109 additions and 18 deletions

View file

@ -1,7 +1,7 @@
--- ---
synopsis: Deprecated language features synopsis: Deprecated language features
issues: [fj#437] issues: [fj#437, 861]
cls: [1785, 1736, 1735, 1744] cls: [1785, 1736, 1735, 1744, 2206]
category: Breaking Changes category: Breaking Changes
credits: [piegames, horrors] credits: [piegames, horrors]
--- ---
@ -15,3 +15,4 @@ and can be disabled via the flags for backwards compatibility (opt-out with `--e
- `rec-set-overrides`: **__overrides** is an old arcane syntax which has not been in use for more than a decade. - `rec-set-overrides`: **__overrides** is an old arcane syntax which has not been in use for more than a decade.
It is soft-deprecated with a warning only, with the plan to turn that into an error in a future release. It is soft-deprecated with a warning only, with the plan to turn that into an error in a future release.
- `ancient-let`: **The old `let` syntax** (`let { body = …; … }`) is soft-deprecated with a warning as well. Use the regular `let … in` instead. - `ancient-let`: **The old `let` syntax** (`let { body = …; … }`) is soft-deprecated with a warning as well. Use the regular `let … in` instead.
- `shadow-internal-symbols`: Arithmetic expressions like `5 - 3` internally expand to `__sub 5 3`, where `__sub` maps to a subtraction builtin. Shadowing such a symbols would affect the evaluation of such operations, but in a very inconsistent way, and is therefore deprecated now. **Affected symbols are:** `__sub`, `__mul`, `__div`, `__lessThan`, `__findFile` and `__nixPath`. Note that these symbols may still be used as variable names as long as they do not shadow internal operations, so e.g. `let __sub = x: y: x + y; in __sub 3 5` remains valid code.

View file

@ -342,6 +342,17 @@ void ExprVar::bindVars(EvalState & es, const std::shared_ptr<const StaticEnv> &
} else { } else {
auto i = curEnv->find(name); auto i = curEnv->find(name);
if (i != curEnv->vars.end()) { if (i != curEnv->vars.end()) {
if (this->needsRoot && !curEnv->isRoot) {
throw ParseError({
.msg = HintFmt(
"Shadowing symbol '%s' used in internal expressions is not allowed. Use %s to disable this error.",
es.symbols[name],
"--extra-deprecated-features shadow-internal-symbols"
),
.pos = es.positions[pos]
});
}
this->level = level; this->level = level;
displ = i->second; displ = i->second;
return; return;

View file

@ -132,8 +132,14 @@ struct ExprVar : Expr
Level level; Level level;
Displacement displ; Displacement displ;
ExprVar(Symbol name) : name(name) { }; /* Variables like `__sub` as generated from expressions like `5 - 3` shouldn't be overridden.
ExprVar(const PosIdx & pos, Symbol name) : pos(pos), name(name) { }; * This is implemented by having a blessed "root" env that contains the definitions (usually `builtins`)
* and checking that this var only binds against that env.
*/
bool needsRoot;
ExprVar(Symbol name) : name(name), needsRoot(false) { };
ExprVar(const PosIdx & pos, Symbol name, bool needsRoot = false) : pos(pos), name(name), needsRoot(needsRoot) { };
Value * maybeThunk(EvalState & state, Env & env) override; Value * maybeThunk(EvalState & state, Env & env) override;
PosIdx getPos() const override { return pos; } PosIdx getPos() const override { return pos; }
COMMON_METHODS COMMON_METHODS
@ -476,6 +482,9 @@ struct StaticEnv
typedef std::vector<std::pair<Symbol, Displacement>> Vars; typedef std::vector<std::pair<Symbol, Displacement>> Vars;
Vars vars; Vars vars;
/* See ExprVar::needsRoot */
bool isRoot = false;
StaticEnv(ExprWith * isWith, const StaticEnv * up, size_t expectedSize = 0) : isWith(isWith), up(up) { StaticEnv(ExprWith * isWith, const StaticEnv * up, size_t expectedSize = 0) : isWith(isWith), up(up) {
vars.reserve(expectedSize); vars.reserve(expectedSize);
}; };

View file

@ -89,12 +89,12 @@ struct ExprState
return std::make_unique<Op>(pos, std::move(left), std::move(right)); return std::make_unique<Op>(pos, std::move(left), std::move(right));
} }
std::unique_ptr<Expr> call(PosIdx pos, Symbol fn, bool flip = false) std::unique_ptr<Expr> call(PosIdx pos, State & state, Symbol fn, bool flip = false)
{ {
std::vector<std::unique_ptr<Expr>> args(2); std::vector<std::unique_ptr<Expr>> args(2);
args[flip ? 0 : 1] = popExprOnly(); args[flip ? 0 : 1] = popExprOnly();
args[flip ? 1 : 0] = popExprOnly(); args[flip ? 1 : 0] = popExprOnly();
return std::make_unique<ExprCall>(pos, std::make_unique<ExprVar>(fn), std::move(args)); return std::make_unique<ExprCall>(pos, state.mkInternalVar(pos, fn), std::move(args));
} }
std::unique_ptr<Expr> pipe(PosIdx pos, State & state, bool flip = false) std::unique_ptr<Expr> pipe(PosIdx pos, State & state, bool flip = false)
@ -122,7 +122,7 @@ struct ExprState
std::unique_ptr<Expr> order(PosIdx pos, bool less, State & state) std::unique_ptr<Expr> order(PosIdx pos, bool less, State & state)
{ {
return call(pos, state.s.lessThan, !less); return call(pos, state, state.s.lessThan, !less);
} }
std::unique_ptr<Expr> concatStrings(PosIdx pos) std::unique_ptr<Expr> concatStrings(PosIdx pos)
@ -138,7 +138,7 @@ struct ExprState
std::vector<std::unique_ptr<Expr>> args(2); std::vector<std::unique_ptr<Expr>> args(2);
args[0] = std::make_unique<ExprInt>(0); args[0] = std::make_unique<ExprInt>(0);
args[1] = popExprOnly(); args[1] = popExprOnly();
return std::make_unique<ExprCall>(pos, std::make_unique<ExprVar>(state.s.sub), std::move(args)); return std::make_unique<ExprCall>(pos, state.mkInternalVar(pos, state.s.sub), std::move(args));
} }
std::pair<PosIdx, std::unique_ptr<Expr>> applyOp(PosIdx pos, auto & op, State & state) { std::pair<PosIdx, std::unique_ptr<Expr>> applyOp(PosIdx pos, auto & op, State & state) {
@ -163,9 +163,9 @@ struct ExprState
[&] (Op::update) { return applyBinary<ExprOpUpdate>(pos); }, [&] (Op::update) { return applyBinary<ExprOpUpdate>(pos); },
[&] (Op::not_) { return applyUnary<ExprOpNot>(); }, [&] (Op::not_) { return applyUnary<ExprOpNot>(); },
[&] (Op::plus) { return concatStrings(pos); }, [&] (Op::plus) { return concatStrings(pos); },
[&] (Op::minus) { return call(pos, state.s.sub); }, [&] (Op::minus) { return call(pos, state, state.s.sub); },
[&] (Op::mul) { return call(pos, state.s.mul); }, [&] (Op::mul) { return call(pos, state, state.s.mul); },
[&] (Op::div) { return call(pos, state.s.div); }, [&] (Op::div) { return call(pos, state, state.s.div); },
[&] (Op::concat) { return applyBinary<ExprOpConcatLists>(pos); }, [&] (Op::concat) { return applyBinary<ExprOpConcatLists>(pos); },
[&] (has_attr & a) { return applyUnary<ExprOpHasAttr>(std::move(a.path)); }, [&] (has_attr & a) { return applyUnary<ExprOpHasAttr>(std::move(a.path)); },
[&] (Op::unary_minus) { return negate(pos, state); }, [&] (Op::unary_minus) { return negate(pos, state); },
@ -608,14 +608,15 @@ template<> struct BuildAST<grammar::v1::path::home_anchor> {
template<> struct BuildAST<grammar::v1::path::searched_path> { template<> struct BuildAST<grammar::v1::path::searched_path> {
static void apply(const auto & in, StringState & s, State & ps) { static void apply(const auto & in, StringState & s, State & ps) {
auto pos = ps.at(in);
std::vector<std::unique_ptr<Expr>> args{2}; std::vector<std::unique_ptr<Expr>> args{2};
args[0] = std::make_unique<ExprVar>(ps.s.nixPath); args[0] = ps.mkInternalVar(pos, ps.s.nixPath);
args[1] = std::make_unique<ExprString>(in.string()); args[1] = std::make_unique<ExprString>(in.string());
s.parts.emplace_back( s.parts.emplace_back(
ps.at(in), pos,
std::make_unique<ExprCall>( std::make_unique<ExprCall>(
ps.at(in), pos,
std::make_unique<ExprVar>(ps.s.findFile), ps.mkInternalVar(pos, ps.s.findFile),
std::move(args))); std::move(args)));
} }
}; };

View file

@ -39,6 +39,12 @@ struct State
std::unique_ptr<Formals> validateFormals(std::unique_ptr<Formals> formals, PosIdx pos = noPos, Symbol arg = {}); std::unique_ptr<Formals> validateFormals(std::unique_ptr<Formals> formals, PosIdx pos = noPos, Symbol arg = {});
std::unique_ptr<Expr> stripIndentation(const PosIdx pos, std::vector<IndStringLine> && line); std::unique_ptr<Expr> stripIndentation(const PosIdx pos, std::vector<IndStringLine> && line);
/* Creates an ExprVar or an ExprVarRoot depending on the feature settings.
* The symbol is synthetic, but for the purpose of error handling the pos is required
* and should point to the expression where the var is used
*/
inline std::unique_ptr<ExprVar> mkInternalVar(PosIdx pos, Symbol name);
// lazy positioning means we don't get byte offsets directly, in.position() would work // lazy positioning means we don't get byte offsets directly, in.position() would work
// but also requires line and column (which is expensive) // but also requires line and column (which is expensive)
PosIdx at(const auto & in) PosIdx at(const auto & in)
@ -52,6 +58,10 @@ struct State
} }
}; };
std::unique_ptr<ExprVar> State::mkInternalVar(PosIdx pos, Symbol name) {
return std::make_unique<ExprVar>(pos, name, !featureSettings.isEnabled(Dep::ShadowInternalSymbols));
}
inline void State::dupAttr(const AttrPath & attrPath, const PosIdx pos, const PosIdx prevPos) inline void State::dupAttr(const AttrPath & attrPath, const PosIdx pos, const PosIdx prevPos)
{ {
throw ParseError({ throw ParseError({

View file

@ -2845,6 +2845,7 @@ void EvalState::createBaseEnv()
baseEnv.values[0]->attrs->sort(); baseEnv.values[0]->attrs->sort();
staticBaseEnv->sort(); staticBaseEnv->sort();
staticBaseEnv->isRoot = true;
/* Note: we have to initialize the 'derivation' constant *after* /* Note: we have to initialize the 'derivation' constant *after*
building baseEnv/staticBaseEnv because it uses 'builtins'. */ building baseEnv/staticBaseEnv because it uses 'builtins'. */

View file

@ -0,0 +1,5 @@
---
name: shadow-internal-symbols
internalName: ShadowInternalSymbols
---
Allow shadowing the symbols `__sub`, `__mul`, `__div`, `__lessThan`, `__findFile` and `__nixPath` when used in the internal AST expansion. (`5 - 3` expands to `__sub 5 3` etc.)

View file

@ -155,6 +155,7 @@ deprecated_feature_definitions = files(
'deprecated-features/ancient-let.md', 'deprecated-features/ancient-let.md',
'deprecated-features/rec-set-overrides.md', 'deprecated-features/rec-set-overrides.md',
'deprecated-features/url-literals.md', 'deprecated-features/url-literals.md',
'deprecated-features/shadow-internal-symbols.md',
) )
experimental_features_gen = custom_target( experimental_features_gen = custom_target(

View file

@ -0,0 +1 @@
[ 8 "multiplied" "subtracted" "subtracted" "divided" "compared" ]

View file

@ -0,0 +1 @@
--extra-deprecated-features shadow-internal-symbols

View file

@ -0,0 +1,14 @@
let
__sub = _: _: "subtracted";
__mul = _: _: "multiplied";
__div = _: _: "divided";
__lessThan = _: _: "compared";
in
[
(3 + 5)
(4 * 4)
(-3)
(12 - 3)
(0 / 1)
(42 < 16)
]

View file

@ -1 +1 @@
-I lang/dir1 -I lang/dir2 -I dir5=lang/dir3 --extra-deprecated-features shadow-internal-symbols -I lang/dir1 -I lang/dir2 -I dir5=lang/dir3

View file

@ -0,0 +1 @@
(__sub (7 + (__div (__mul (__sub 0 5) 12) 3)) 1)

View file

@ -0,0 +1 @@
7 + (-5) * 12 / 3 - 1

View file

@ -14,8 +14,8 @@ nix-instantiate --restrict-eval --eval -E 'builtins.readFile ./simple.nix' -I sr
(! nix-instantiate --restrict-eval --eval -E 'builtins.readDir ../../lix/legacy') (! nix-instantiate --restrict-eval --eval -E 'builtins.readDir ../../lix/legacy')
nix-instantiate --restrict-eval --eval -E 'builtins.readDir ../../lix/legacy' -I src=../../lix nix-instantiate --restrict-eval --eval -E 'builtins.readDir ../../lix/legacy' -I src=../../lix
(! nix-instantiate --restrict-eval --eval -E 'let __nixPath = [ { prefix = "foo"; path = ./.; } ]; in <foo>') (! nix-instantiate --extra-deprecated-features shadow-internal-symbols --restrict-eval --eval -E 'let __nixPath = [ { prefix = "foo"; path = ./.; } ]; in <foo>')
nix-instantiate --restrict-eval --eval -E 'let __nixPath = [ { prefix = "foo"; path = ./.; } ]; in <foo>' -I src=. nix-instantiate --extra-deprecated-features shadow-internal-symbols --restrict-eval --eval -E 'let __nixPath = [ { prefix = "foo"; path = ./.; } ]; in <foo>' -I src=.
p=$(nix eval --raw --expr "builtins.fetchurl \"file://$(pwd)/restricted.sh\"" --impure --restrict-eval --allowed-uris "file://$(pwd)") p=$(nix eval --raw --expr "builtins.fetchurl \"file://$(pwd)/restricted.sh\"" --impure --restrict-eval --allowed-uris "file://$(pwd)")
cmp $p restricted.sh cmp $p restricted.sh

View file

@ -246,4 +246,38 @@ namespace nix {
v = eval("let add = l: r: l + r; in ''a'' |> add <| ''c''", true, mockFeatureSettings); v = eval("let add = l: r: l + r; in ''a'' |> add <| ''c''", true, mockFeatureSettings);
ASSERT_THAT(v, IsStringEq("ac")); ASSERT_THAT(v, IsStringEq("ac"));
} }
TEST_F(TrivialExpressionTest, shadowInternalSymbols) {
FeatureSettings mockFeatureSettings;
mockFeatureSettings.set("deprecated-features", "shadow-internal-symbols");
auto v = eval("let __sub = _: _: ''subtracted''; in -3", true, mockFeatureSettings);
ASSERT_THAT(v, IsStringEq("subtracted"));
ASSERT_THROW(eval("let __sub = _: _: ''subtracted''; in -3"), Error);
v = eval("let __sub = _: _: ''subtracted''; in 12 - 3", true, mockFeatureSettings);
ASSERT_THAT(v, IsStringEq("subtracted"));
ASSERT_THROW(eval("let __sub = _: _: ''subtracted''; in 12 - 3"), Error);
v = eval("let __mul = _: _: ''multiplied''; in 4 * 4", true, mockFeatureSettings);
ASSERT_THAT(v, IsStringEq("multiplied"));
ASSERT_THROW(eval("let __mul = _: _: ''multiplied''; in 4 * 4"), Error);
v = eval("let __div = _: _: ''divided''; in 0 / 1", true, mockFeatureSettings);
ASSERT_THAT(v, IsStringEq("divided"));
ASSERT_THROW(eval("let __div = _: _: ''divided''; in 0 / 1"), Error);
v = eval("let __lessThan = _: _: ''compared''; in 42 < 16", true, mockFeatureSettings);
ASSERT_THAT(v, IsStringEq("compared"));
ASSERT_THROW(eval("let __lessThan = _: _: ''compared''; in 42 < 16"), Error);
v = eval(
"let __findFile = path: entry: assert path == ''foo''; entry; __nixPath = ''foo''; in <bar>",
true,
mockFeatureSettings
);
ASSERT_THAT(v, IsStringEq("bar"));
ASSERT_THROW(eval("let __findFile = _: _: ''found''; in import <foo>"), Error);
ASSERT_THROW(eval("let __nixPath = null; in import <foo>"), Error);
}
} /* namespace nix */ } /* namespace nix */