Skip to content

Commit 3c3f740

Browse files
authored
Merge pull request #22120 from asgerf/unified/local-scoping
Unified: implement local scoping
2 parents 134e302 + bfb3ead commit 3c3f740

20 files changed

Lines changed: 1189 additions & 75 deletions

ruby/ql/lib/codeql/ruby/ast/internal/Variable.qll

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -170,8 +170,6 @@ private module Input implements LocalNameBindingInputSig<Location> {
170170
class SiblingShadowingDecl extends AstNode {
171171
SiblingShadowingDecl() { none() }
172172

173-
AstNode getLhs() { none() }
174-
175173
AstNode getRhs() { none() }
176174

177175
AstNode getElse() { none() }

shared/namebinding/codeql/namebinding/LocalNameBinding.qll

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -82,9 +82,6 @@ signature module LocalNameBindingInputSig<LocationSig Location> {
8282
* ```
8383
*/
8484
class SiblingShadowingDecl extends AstNode {
85-
/** Gets the left-hand side of this declaration. */
86-
AstNode getLhs();
87-
8885
/**
8986
* Gets the right-hand side of this declaration.
9087
*
@@ -296,6 +293,31 @@ module LocalNameBinding<LocationSig Location, LocalNameBindingInputSig<Location>
296293
)
297294
}
298295

296+
/** Holds if `node` should be included in the debug tree. */
297+
private signature predicate relevantNodeSig(AstNode node);
298+
299+
module DebugScopeGraph<relevantNodeSig/1 relevantNode> {
300+
private string getANodeAnnotation(AstNode node) {
301+
result =
302+
"[scope=" +
303+
strictconcat(string name |
304+
declInScope(_, name, node) or implicitDeclInScope(name, node)
305+
|
306+
name, ","
307+
) + "]"
308+
}
309+
310+
query predicate nodes(AstNode node, string key, string value) {
311+
relevantNode(node) and
312+
key = "semmle.label" and
313+
value = node.toString() + concat(getANodeAnnotation(node))
314+
}
315+
316+
query predicate edges(AstNode node1, AstNode node2) {
317+
relevantNode(node2) and node2 = getParentForScoping(node1)
318+
}
319+
}
320+
299321
/** Gets the immediately enclosing variable scope of `n`. */
300322
private Scope getEnclosingScope(AstNode n) {
301323
result = getParentForScoping(n)

shared/yeast/src/build.rs

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,37 @@ impl<C: Clone> BuildCtx<'_, C> {
177177
None => Err("translate() called on a BuildCtx without a translator handle".into()),
178178
}
179179
}
180+
181+
/// Translate every node in an iterator with a **fresh** user context
182+
/// (reset to `C::default()`), restoring the previous context afterwards.
183+
///
184+
/// Use when descending into a subtree — a body, expression, or statement
185+
/// list — that must not inherit any of the surrounding translation
186+
/// context (for example an enclosing binding modifier). Accepts optional
187+
/// (`Option<Id>`) and repeated (`Vec<Id>`) captures (both `IntoIterator`);
188+
/// for a single `Id`, wrap it in `std::iter::once(id)`.
189+
pub fn translate_reset<I: Into<Id>>(
190+
&mut self,
191+
ids: impl IntoIterator<Item = I>,
192+
) -> Result<Vec<Id>, String>
193+
where
194+
C: Default,
195+
{
196+
let saved = std::mem::take(&mut *self.user_ctx);
197+
let mut out = Vec::new();
198+
let mut result = Ok(());
199+
for id in ids {
200+
match self.translate(id) {
201+
Ok(v) => out.extend(v),
202+
Err(e) => {
203+
result = Err(e);
204+
break;
205+
}
206+
}
207+
}
208+
*self.user_ctx = saved;
209+
result.map(|()| out)
210+
}
180211
}
181212

182213
impl<C> std::ops::Deref for BuildCtx<'_, C> {

unified/extractor/src/languages/swift/swift.rs

Lines changed: 85 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -25,23 +25,34 @@ struct SwiftContext {
2525
/// by the outer `function_parameter` rule; read by the `parameter`
2626
/// rules.
2727
default_value: Option<yeast::Id>,
28-
/// Translated outer modifiers (e.g. visibility, attributes) to
29-
/// attach to each child of a flattening outer rule. Set by
30-
/// `property_declaration`, `enum_entry`, and
31-
/// `protocol_property_declaration`.
28+
/// Translated outer modifiers to attach to each child of a flattening
29+
/// outer rule. Set by `property_declaration`, `binding_pattern`,
30+
/// `enum_entry`, and `protocol_property_declaration`. For `let`/`var`
31+
/// declarations and `binding_pattern`s the list is led by the binding
32+
/// modifier, which also serves as the "this is a binding" signal for
33+
/// pattern translation (see `in_binding_pattern`).
3234
outer_modifiers: Vec<yeast::Id>,
33-
/// The `let`/`var` binding modifier for a `property_declaration`.
34-
/// Set by `property_declaration`; read by the inner declaration
35-
/// rules (`property_binding` variants, accessor rules) so they
36-
/// emit it as part of the output node's `modifier:` field.
37-
binding_modifier: Option<yeast::Id>,
3835
/// True when the current child of a flattening outer rule is not
3936
/// the first one — its inner rule should emit a
4037
/// `chained_declaration` modifier so the original grouping can be
4138
/// recovered downstream.
4239
is_chained: bool,
4340
}
4441

42+
impl SwiftContext {
43+
/// Whether the pattern currently being translated is a binding
44+
/// (the LHS of a `let`/`var` declaration or a `binding_pattern`).
45+
///
46+
/// True exactly when an enclosing binding has published its modifier into
47+
/// `outer_modifiers`. This is reliable because non-binding subtrees
48+
/// (bodies, initializer values, ...) are translated with a reset context
49+
/// (see `BuildCtx::translate_reset`), so a bare identifier only sees a
50+
/// non-empty `outer_modifiers` when it really is a binding.
51+
fn in_binding_pattern(&self) -> bool {
52+
!self.outer_modifiers.is_empty()
53+
}
54+
}
55+
4556
/// Build a freshly-created `chained_declaration` modifier node if
4657
/// `ctx.is_chained`, else `None`. Used by inner declaration rules to
4758
/// emit the chained tag for non-first children of a flattening outer
@@ -220,16 +231,15 @@ fn translation_rules() -> Vec<Rule<SwiftContext>> {
220231
(property_binding
221232
name: (pattern bound_identifier: @name)
222233
type: _? @ty
223-
computed_value: (computed_property statement: _* @body))
234+
computed_value: (computed_property statement: _* @@body))
224235
=>
225236
(accessor_declaration
226-
modifier: {ctx.binding_modifier}
227237
modifier: {ctx.outer_modifiers.clone()}
228238
modifier: {chained_modifier(&mut ctx)}
229239
name: (identifier #{name})
230240
type: {ty}
231241
accessor_kind: (accessor_kind "get")
232-
body: (block stmt: {body}))
242+
body: (block stmt: {ctx.translate_reset(body)?}))
233243
),
234244
// Stored property with willSet/didSet observers (initializer
235245
// optional) → a `variable_declaration` followed by one
@@ -246,13 +256,19 @@ fn translation_rules() -> Vec<Rule<SwiftContext>> {
246256
(property_binding
247257
name: (pattern bound_identifier: @name)
248258
type: _? @ty
249-
value: _? @val
259+
value: _? @@val
250260
observers: (willset_didset_block willset: _? @@ws didset: _? @@ds))
251261
=>
252262
{{
263+
// The initializer value must not inherit the binding context
264+
// (it may contain patterns, e.g. a switch expression), so
265+
// translate it with a reset context. The observers keep the
266+
// context: each willSet/didSet accessor emits the binding
267+
// modifier and resets its own body.
268+
let val = ctx.translate_reset(val)?;
269+
253270
let var_decl = tree!(
254271
(variable_declaration
255-
modifier: {ctx.binding_modifier}
256272
modifier: {ctx.outer_modifiers.clone()}
257273
modifier: {chained_modifier(&mut ctx)}
258274
pattern: (name_pattern identifier: (identifier #{name}))
@@ -275,19 +291,23 @@ fn translation_rules() -> Vec<Rule<SwiftContext>> {
275291
),
276292
// property_binding with any pattern name (identifier or
277293
// destructuring). Reads outer modifiers / chained tag from `ctx`.
294+
//
295+
// The enclosing `property_declaration` leads `ctx.outer_modifiers`
296+
// with the `let`/`var` binding modifier, so the auto-translated name
297+
// pattern (the LHS) becomes a binding, while the initializer value is
298+
// translated with a reset context (see `translate_reset`).
278299
rule!(
279300
(property_binding
280301
name: @pattern
281302
type: _? @ty
282-
value: _? @val)
303+
value: _? @@val)
283304
=>
284305
(variable_declaration
285-
modifier: {ctx.binding_modifier}
286306
modifier: {ctx.outer_modifiers.clone()}
287307
modifier: {chained_modifier(&mut ctx)}
288308
pattern: {pattern}
289309
type: {ty}
290-
value: {val})
310+
value: {ctx.translate_reset(val)?}) // reset context: the initializer must not see the binding
291311
),
292312
// property_declaration: flatten declarators (each may translate
293313
// to multiple nodes — variable_declaration and/or
@@ -307,8 +327,11 @@ fn translation_rules() -> Vec<Rule<SwiftContext>> {
307327
=>
308328
{{
309329
let binding_text = ctx.ast.source_text(binding_kind);
310-
ctx.binding_modifier = Some(ctx.literal("modifier", &binding_text));
311-
ctx.outer_modifiers = mods;
330+
let binding = ctx.literal("modifier", &binding_text);
331+
// The `let`/`var` binding modifier leads the declaration's
332+
// modifier list and doubles as the "this is a binding" signal
333+
// for pattern translation (see `in_binding_pattern`).
334+
ctx.outer_modifiers = std::iter::once(binding).chain(mods).collect();
312335

313336
let mut result = Vec::new();
314337
for (i, decl) in decls.into_iter().enumerate() {
@@ -403,14 +426,23 @@ fn translation_rules() -> Vec<Rule<SwiftContext>> {
403426
rule!((type name: @inner) => {inner}),
404427
// `directly_assignable_expression` is just a wrapper; unwrap it
405428
rule!((directly_assignable_expression expr: @inner) => {inner}),
406-
// Pattern with bound_identifier → name_pattern
407-
rule!((pattern bound_identifier: @name) => (name_pattern identifier: (identifier #{name}))),
408-
// Pattern with 'let' or 'var' binding: extract the inner pattern
409-
// TODO: Names in a pattern need to be translated to expr_equality_pattern if not under a 'var/let' but we lack a way to pass down context to do this.
429+
// Pattern with bound_identifier → name_pattern.
430+
rule!(
431+
(pattern bound_identifier: @name)
432+
=>
433+
(name_pattern identifier: (identifier #{name}))
434+
),
435+
// Pattern with 'let' or 'var' binding: publish the binding modifier
436+
// into `ctx` and translate the inner pattern under it.
410437
rule!(
411-
(pattern kind: (binding_pattern binding: _? pattern: @pattern))
438+
(pattern kind: (binding_pattern binding: (value_binding_pattern mutability: @@binding_kind) pattern: @@pattern))
412439
=>
413-
{pattern}
440+
{{
441+
let binding_text = ctx.ast.source_text(binding_kind);
442+
let binding = ctx.literal("modifier", &binding_text);
443+
ctx.outer_modifiers = vec![binding];
444+
ctx.translate(pattern)?
445+
}}
414446
),
415447
// case T.foo(x,y) pattern
416448
rule!(
@@ -436,6 +468,22 @@ fn translation_rules() -> Vec<Rule<SwiftContext>> {
436468
rule!((pattern kind: (type_casting_pattern)) => (unsupported_node)),
437469
// Wildcard pattern
438470
rule!((pattern kind: (wildcard_pattern)) => (ignore_pattern)),
471+
// A bare identifier used as an expression-pattern. Under a `var`/`let`
472+
// binding it introduces a new variable and becomes a `name_pattern`;
473+
// otherwise it matches by equality and is left as an `expr_equality_pattern`
474+
// over the name expression.
475+
rule!(
476+
(pattern kind: (simple_identifier) @name)
477+
=>
478+
{
479+
if ctx.in_binding_pattern() {
480+
tree!((name_pattern identifier: (identifier #{name})))
481+
} else {
482+
let expr = tree!((name_expr identifier: (identifier #{name})));
483+
tree!((expr_equality_pattern expr: {expr}))
484+
}
485+
}
486+
),
439487
// Expression pattern
440488
// We lack a way to check if 'expr' is actually an expression, but due to rule ordering
441489
// the 'expression' case is the only remaining possibility when this rule tries to match.
@@ -1062,56 +1110,52 @@ fn translation_rules() -> Vec<Rule<SwiftContext>> {
10621110
// and binding/outer modifiers + chained tag from the outer
10631111
// property_declaration rule.
10641112
rule!(
1065-
(computed_getter body: (block statement: _* @body)?)
1113+
(computed_getter body: (block statement: _* @@body)?)
10661114
=>
10671115
(accessor_declaration
1068-
modifier: {ctx.binding_modifier}
10691116
modifier: {ctx.outer_modifiers.clone()}
10701117
modifier: {chained_modifier(&mut ctx)}
10711118
name: {ctx.property_name.ok_or("computed_getter outside property_binding context")?}
10721119
type: {ctx.property_type}
10731120
accessor_kind: (accessor_kind "get")
1074-
body: (block stmt: {body}))
1121+
body: (block stmt: {ctx.translate_reset(body)?}))
10751122
),
10761123
// Computed setter with explicit parameter name.
10771124
rule!(
1078-
(computed_setter parameter: @param body: (block statement: _* @body))
1125+
(computed_setter parameter: @param body: (block statement: _* @@body))
10791126
=>
10801127
(accessor_declaration
1081-
modifier: {ctx.binding_modifier}
10821128
modifier: {ctx.outer_modifiers.clone()}
10831129
modifier: {chained_modifier(&mut ctx)}
10841130
name: {ctx.property_name.ok_or("computed_setter outside property_binding context")?}
10851131
type: {ctx.property_type}
10861132
accessor_kind: (accessor_kind "set")
10871133
parameter: (parameter pattern: (name_pattern identifier: (identifier #{param})))
1088-
body: (block stmt: {body}))
1134+
body: (block stmt: {ctx.translate_reset(body)?}))
10891135
),
10901136
// Computed setter without explicit parameter name; body optional.
10911137
rule!(
1092-
(computed_setter body: (block statement: _* @body)?)
1138+
(computed_setter body: (block statement: _* @@body)?)
10931139
=>
10941140
(accessor_declaration
1095-
modifier: {ctx.binding_modifier}
10961141
modifier: {ctx.outer_modifiers.clone()}
10971142
modifier: {chained_modifier(&mut ctx)}
10981143
name: {ctx.property_name.ok_or("computed_setter outside property_binding context")?}
10991144
type: {ctx.property_type}
11001145
accessor_kind: (accessor_kind "set")
1101-
body: (block stmt: {body}))
1146+
body: (block stmt: {ctx.translate_reset(body)?}))
11021147
),
11031148
// Computed modify → accessor_declaration
11041149
rule!(
1105-
(computed_modify body: (block statement: _* @body))
1150+
(computed_modify body: (block statement: _* @@body))
11061151
=>
11071152
(accessor_declaration
1108-
modifier: {ctx.binding_modifier}
11091153
modifier: {ctx.outer_modifiers.clone()}
11101154
modifier: {chained_modifier(&mut ctx)}
11111155
name: {ctx.property_name.ok_or("computed_modify outside property_binding context")?}
11121156
type: {ctx.property_type}
11131157
accessor_kind: (accessor_kind "modify")
1114-
body: (block stmt: {body}))
1158+
body: (block stmt: {ctx.translate_reset(body)?}))
11151159
),
11161160
// willset/didset block — spread to children (only reachable as a
11171161
// fallback; the outer property_binding manual rule normally
@@ -1122,27 +1166,25 @@ fn translation_rules() -> Vec<Rule<SwiftContext>> {
11221166
// binding/outer modifiers + chained tag from the outer
11231167
// property_declaration rule.
11241168
rule!(
1125-
(willset_clause body: (block statement: _* @body)?)
1169+
(willset_clause body: (block statement: _* @@body)?)
11261170
=>
11271171
(accessor_declaration
1128-
modifier: {ctx.binding_modifier}
11291172
modifier: {ctx.outer_modifiers.clone()}
11301173
modifier: {chained_modifier(&mut ctx)}
11311174
name: {ctx.property_name.ok_or("willset_clause outside property_binding context")?}
11321175
accessor_kind: (accessor_kind "willSet")
1133-
body: (block stmt: {body}))
1176+
body: (block stmt: {ctx.translate_reset(body)?}))
11341177
),
11351178
// didset clause → accessor_declaration (body optional).
11361179
rule!(
1137-
(didset_clause body: (block statement: _* @body)?)
1180+
(didset_clause body: (block statement: _* @@body)?)
11381181
=>
11391182
(accessor_declaration
1140-
modifier: {ctx.binding_modifier}
11411183
modifier: {ctx.outer_modifiers.clone()}
11421184
modifier: {chained_modifier(&mut ctx)}
11431185
name: {ctx.property_name.ok_or("didset_clause outside property_binding context")?}
11441186
accessor_kind: (accessor_kind "didSet")
1145-
body: (block stmt: {body}))
1187+
body: (block stmt: {ctx.translate_reset(body)?}))
11461188
),
11471189
// Preprocessor conditionals — unsupported
11481190
rule!((diagnostic) => (unsupported_node)),

0 commit comments

Comments
 (0)