Shared: Local name resolution library#21873
Conversation
46e6643 to
78a5319
Compare
|
|
||
| /** Gets the name of this variable as a string. */ | ||
| string getText() { result = text } | ||
| predicate accessCand(AstNode n, string name) { |
0de851d to
35ff093
Compare
35ff093 to
cad848a
Compare
| * We also move any `else` branch _before_ the condition to ensure that shadowing declarations | ||
| * inside the condition are not in scope. | ||
| */ | ||
| private AstNode getChildAdj(AstNode parent, int index) { |
9ce2e74 to
baef7eb
Compare
|
|
||
| class Ranked = AstNode; | ||
|
|
||
| int getRank(C parent, Ranked child) { |
59b8380 to
0f3ee22
Compare
Rerun has been triggered: 5 restarted 🚀 |
Rerun has been triggered: 1 restarted 🚀 |
0f3ee22 to
b5586e5
Compare
| ) | ||
| } | ||
|
|
||
| predicate accessCand(AstNode n, string name) { |
da949b1 to
392fab1
Compare
392fab1 to
128dd5d
Compare
| /** | ||
| * Holds if `n` is a node that may access a local named `name`. | ||
| */ | ||
| predicate accessCand(AstNode n, string name); |
Rerun has been triggered: 2 restarted 🚀 |
cba5d4e to
aa128c1
Compare
aa128c1 to
0937133
Compare
Rerun has been triggered: 2 restarted 🚀 |
There was a problem hiding this comment.
Pull request overview
This PR introduces a shared local name-binding library and wires it into Ruby and Rust variable resolution, replacing language-specific scoping logic with a common sibling-shadowing-aware implementation.
Changes:
- Adds
codeql/namebindingshared qlpack andLocalNameBinding.qll. - Refactors Rust and Ruby local variable/access resolution to use the shared library.
- Expands Rust and Ruby variable-scope tests for shadowing, let chains, exception variables, and block parameters.
Show a summary per file
| File | Description |
|---|---|
shared/namebinding/qlpack.yml |
Defines the new shared namebinding library pack. |
shared/namebinding/codeql/namebinding/LocalNameBinding.qll |
Adds the shared local name-binding implementation. |
rust/ql/lib/qlpack.yml |
Adds the shared namebinding dependency. |
rust/ql/lib/codeql/rust/elements/internal/VariableImpl.qll |
Replaces Rust-specific variable binding with shared namebinding integration. |
rust/ql/lib/codeql/rust/elements/internal/ParamBaseImpl.qll |
Adds callable lookup support for parameters. |
rust/ql/.gitattributes |
Marks ParamBaseImpl.qll as hand-modified rather than generated. |
rust/ql/.generated.list |
Removes ParamBaseImpl.qll from generated tracking. |
rust/ql/test/library-tests/variables/main.rs |
Adds Rust shadowing and conditional let-chain test cases. |
rust/ql/test/library-tests/variables/variables.expected |
Updates Rust variable test expectations. |
ruby/ql/lib/qlpack.yml |
Adds the shared namebinding dependency. |
ruby/ql/lib/codeql/ruby/ast/Parameter.qll |
Updates parameter-to-variable lookup for the new local variable representation. |
ruby/ql/lib/codeql/ruby/ast/internal/Variable.qll |
Refactors Ruby local variable/access resolution onto shared namebinding. |
ruby/ql/lib/codeql/ruby/ast/internal/Synthesis.qll |
Updates synthetic self/hash-value access handling. |
ruby/ql/lib/codeql/ruby/ast/internal/Scope.qll |
Exposes parent/scope helpers needed by namebinding. |
ruby/ql/lib/codeql/ruby/ast/internal/Parameter.qll |
Updates internal parameter variable lookup. |
ruby/ql/lib/codeql/ruby/ast/internal/AST.qll |
Updates local variable access construction. |
ruby/ql/test/library-tests/variables/scopes.rb |
Adds Ruby exception-variable and parameter-shadowing tests. |
ruby/ql/test/library-tests/variables/varscopes.expected |
Updates Ruby scope expectations. |
ruby/ql/test/library-tests/variables/variable.expected |
Updates Ruby variable expectations. |
ruby/ql/test/library-tests/variables/varaccess.expected |
Updates Ruby variable access expectations. |
ruby/ql/test/library-tests/variables/ssa.expected |
Updates Ruby SSA expectations. |
ruby/ql/test/library-tests/variables/parameter.expected |
Updates Ruby parameter expectations. |
Copilot's findings
- Files reviewed: 22/24 changed files
- Comments generated: 4
asgerf
left a comment
There was a problem hiding this comment.
Looks great! I've reviewed everything, though for Rust maybe get a second reviewer.
The only potential blocker I'd say actually comes from a QL4QL alert about the f field in NestedFunctionAccess being unused, which seems legit.
| * } | ||
| * ``` | ||
| */ | ||
| class Conditional extends AstNode { |
There was a problem hiding this comment.
Perhaps mention how to represent if let Some(x) = opt else { ... } where the effective then-branch is to fall through?
This type of statement is sort-of hinted at in the SiblingShadowingDecl class below, but the feature is orthogonal to sibling shadowing. Swift has the equivalent of if let ... else ... but does not have sibling shadowing.
| or | ||
| isTopScope(this) | ||
| or | ||
| lookupStartsAt(_, this) |
There was a problem hiding this comment.
I'm not sure the last case is needed here? The corresponding parameter of lookupStartsAt is named scope but it's just the AST node where upward traversal begins.
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
This PR adds a new shared library for doing local name binding, and applies it to Ruby and Rust.
The interface to the library is inspired by a similar unreleased library created by @asgerf, but unlike that library, this library supports sibling-based shadowing as known from Rust:
The implementation handles sibling shadowing much simpler than the current Rust implementation (which also has a bug, see one of the added tests), by treating shadowing sibling declarations as defining a new scope, and then letting all subsequent statements be children of that scope. That is, when looking up names by going up the AST, we rewrite the AST above to (eliding leaf nodes):
Note how the RHS is moved away from
let x = x + 1to ensure that it resolves to the preceding declaration.DCA for Rust and Ruby are good; no result changes but a minor speedup (for Rust).