From a6f0e87fa8c848ca16e38c6a0dc03518acfd2625 Mon Sep 17 00:00:00 2001 From: Raito Bezarius Date: Fri, 21 Nov 2025 17:36:35 +0100 Subject: [PATCH] libexpr/static-analyzer: init Change-Id: I606718fbe33db1f6c00ca34f45c6553ce468398c Signed-off-by: Raito Bezarius --- lix/libexpr/nixexpr.cc | 75 ++++++++++++++++++++++++++++++++++++++++++ lix/libexpr/nixexpr.hh | 28 ++++++++++++++++ 2 files changed, 103 insertions(+) diff --git a/lix/libexpr/nixexpr.cc b/lix/libexpr/nixexpr.cc index ec23c0a0c..a0a31b56d 100644 --- a/lix/libexpr/nixexpr.cc +++ b/lix/libexpr/nixexpr.cc @@ -321,6 +321,74 @@ JSON printAttrPathToJson(const SymbolTable & symbols, const AttrPath & attrPath) /* Computing levels/displacements for variables. */ namespace { +// This is a one-pass static analyzer for +// various topics. +struct StaticAnalyzer : ExprVisitor +{ + std::set staticallyUsedVariables; + bool usedDynamicVariables = false; + + StaticAnalyzer() {} + using ExprVisitor::visit; + + void visit(ExprDebugFrame & e, std::unique_ptr & ptr) override + { + visit(e.inner); + } + void visit(ExprLiteral & e, std::unique_ptr & ptr) override {} + void visit(ExprVar & e, std::unique_ptr & ptr) override + { + staticallyUsedVariables.insert(e.name); + } + void visit(ExprInheritFrom & e, std::unique_ptr & ptr) override {} + void visit(ExprSelect & e, std::unique_ptr & ptr) override + { + if (e.isDynamic()) { + usedDynamicVariables = true; + } + + visit(e.def); + visit(e.e); + } + void visit(ExprOpHasAttr & e, std::unique_ptr & ptr) override + { + if (e.isDynamic()) { + usedDynamicVariables = true; + } + + visit(e.e); + } + void visit(ExprSet & e, std::unique_ptr & ptr) override + { + // TODO: oh bro, we need to analyze dynamic attributes for their value expressions. + } + void visit(ExprList & e, std::unique_ptr & ptr) override {} + void visit(ExprLambda & e, std::unique_ptr & ptr) override {} + void visit(ExprCall & e, std::unique_ptr & ptr) override {} + void visit(ExprLet & e, std::unique_ptr & ptr) override {} + void visit(ExprWith & e, std::unique_ptr & ptr) override {} + void visit(ExprIf & e, std::unique_ptr & ptr) override {} + void visit(ExprAssert & e, std::unique_ptr & ptr) override {} + void visit(ExprOpNot & e, std::unique_ptr & ptr) override {} +#define BINOP(type) \ + /* NOLINTNEXTLINE(bugprone-macro-parentheses) */ \ + void visit(type & e, std::unique_ptr & ptr) override \ + { \ + visit(e.e1); \ + visit(e.e2); \ + } + BINOP(ExprOpEq) + BINOP(ExprOpNEq) + BINOP(ExprOpAnd) + BINOP(ExprOpOr) + BINOP(ExprOpImpl) + BINOP(ExprOpUpdate) + BINOP(ExprOpConcatLists) +#undef BINOP + void visit(ExprConcatStrings & e, std::unique_ptr & ptr) override {} + void visit(ExprPos & e, std::unique_ptr & ptr) override {} + void visit(ExprBlackHole & e, std::unique_ptr & ptr) override {} +}; struct VarBinder : ExprVisitor { Evaluator & es; @@ -590,6 +658,13 @@ void VarBinder::visit(ExprLambda & e, std::unique_ptr & ptr) { withEnv(e.pattern->buildEnv(env.get()), [&] { e.pattern->accept(*this); + /* TODO: If statically, e.body makes only use of some parameters and not the whole scope. + * We shouldn't have to keep around all the environment data which might contain trapped + * pointers. Analyze `e.body` and return its statically known set of used variables. + * */ + DirectCallAnalyzer analyzer{es, env}; + analyzer.visit(e.body); + e.shortcut = analyzer.shortcut; visit(e.body); }); } diff --git a/lix/libexpr/nixexpr.hh b/lix/libexpr/nixexpr.hh index 1fc708a5e..7be86fb7d 100644 --- a/lix/libexpr/nixexpr.hh +++ b/lix/libexpr/nixexpr.hh @@ -292,6 +292,17 @@ struct ExprSelect : Expr /** The path of attributes being selected. e.g. `bar.baz` in `foo.bar.baz.` */ AttrPath attrPath; + bool isDynamic() const + { + for (auto & name : attrPath) { + if (name.expr) { + return true; + } + } + + return false; + } + ExprSelect(const PosIdx & pos, std::unique_ptr e, AttrPath attrPath, std::unique_ptr def) : Expr(pos), e(std::move(e)), def(std::move(def)), attrPath(std::move(attrPath)) { }; ExprSelect(const PosIdx & pos, std::unique_ptr e, const PosIdx namePos, Symbol name) : Expr(pos), e(std::move(e)) { attrPath.push_back(AttrName(namePos, name)); }; JSON toJSON(const SymbolTable & symbols) const override; @@ -303,6 +314,16 @@ struct ExprOpHasAttr : Expr { std::unique_ptr e; AttrPath attrPath; + + bool isDynamic() const + { + for (auto & name : attrPath) { + if (name.expr) { + return true; + } + } + return false; + } ExprOpHasAttr(const PosIdx & pos, std::unique_ptr e, AttrPath attrPath) : Expr(pos), e(std::move(e)), attrPath(std::move(attrPath)) { }; JSON toJSON(const SymbolTable & symbols) const override; void eval(EvalState & state, Env & env, Value & v) override; @@ -479,6 +500,13 @@ struct ExprLambda : Expr Symbol name; std::unique_ptr pattern; std::unique_ptr body; + // This is a shortcut variant which + // exhausts the body further lambda constructions + // to transform x1: x2: …: xn: b + // into { x1, …, xn }: b + // This can be used when you know that you are + // passing all the arguments at once. + std::unique_ptr shortcut; ExprLambda(PosIdx pos, std::unique_ptr pattern, std::unique_ptr body) : Expr(pos), pattern(std::move(pattern)), body(std::move(body)) {