Skip to content

Commit 824bba2

Browse files
authored
Merge pull request #22896 from asgerf/unified/accessors
Unified: Add data flow support for getters and setters
2 parents 695bfe7 + 1bb7125 commit 824bba2

29 files changed

Lines changed: 1056 additions & 182 deletions

‎unified/extractor/ast_types.yml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -534,7 +534,7 @@ named:
534534
name_node: identifier
535535
accessor_kind: accessor_kind
536536
parameter*: parameter
537-
type?: expr
537+
return_type?: expr
538538
body?: block
539539

540540
# "get", "set", or a language-specific kind like "didSet"

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

Lines changed: 87 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -12,15 +12,15 @@ struct SwiftContext {
1212
/// `variableDecl` rule before translating the accessor block; read by the
1313
/// inner `accessorDecl` rules to name each `accessor_declaration`.
1414
property_name_node: Option<yeast::OutputId>,
15-
/// Translated type node for the property type. Set (for computed
16-
/// properties) by the accessor-bearing `variableDecl` rule; read by the
17-
/// inner `accessorDecl` rules. Left `None` for stored properties with
18-
/// observers, so their `willSet`/`didSet` accessors carry no type.
15+
/// Translated type node for the property type. Set by the
16+
/// accessor-bearing `variableDecl` rule; read by the inner `accessorDecl`
17+
/// rules for both the getter return type and its `newValue`/`oldValue`
18+
/// parameter type, when available.
1919
property_type: Option<yeast::OutputId>,
2020
/// Translated outer modifiers to attach to each child of a flattening
21-
/// outer rule — e.g. the `let`/`var` binding modifier on each
22-
/// `patternBinding` of a `variableDecl`, or the binding modifier on each
23-
/// accessor of a property.
21+
/// outer rule — e.g. the `let`/`var` binding modifier, attributes and
22+
/// modifiers (`static`, `public`, …) on each `patternBinding` of a
23+
/// `variableDecl`, or on each accessor of a property.
2424
outer_modifiers: Vec<yeast::OutputId>,
2525
/// True when the current child of a flattening outer rule is not
2626
/// the first one — its inner rule should emit a
@@ -325,6 +325,8 @@ fn translation_rules() -> Vec<Rule<SwiftContext>> {
325325
// block.
326326
rule!(
327327
(variableDecl
328+
attributes: _* @attrs
329+
modifiers: _* @mods
328330
bindingSpecifier: @@spec
329331
bindings: (patternBinding
330332
pattern: (identifierPattern identifier: @@name)
@@ -333,8 +335,10 @@ fn translation_rules() -> Vec<Rule<SwiftContext>> {
333335
=>
334336
(accessor_declaration
335337
modifier: (modifier #{spec})
338+
modifier: {attrs}
339+
modifier: {mods}
336340
name_node: (identifier #{name})
337-
type: {ty}
341+
return_type: {ty}
338342
accessor_kind: {ctx.literal_at_start_of("accessor_kind", "get", body)}
339343
body: {body})
340344
),
@@ -345,16 +349,18 @@ fn translation_rules() -> Vec<Rule<SwiftContext>> {
345349
// * With an initializer (`var x: T = e { willSet {…} didSet {…} }`) it is
346350
// a *stored* property with observers: emit the backing
347351
// `variable_declaration` first, then one `accessor_declaration` per
348-
// observer (observers carry no type).
352+
// observer.
349353
// * Without an initializer (`var v: T { get set }`, incl. protocol
350-
// requirements) it is a *computed* property: no backing variable; the
351-
// type is published so the get/set accessors carry it.
354+
// requirements) it is a *computed* property: no backing variable.
352355
//
353-
// In both cases the first emitted declaration is unchained and every
356+
// In both cases the type is published so every accessor carries it, and
357+
// the first emitted declaration is unchained and every
354358
// subsequent one is tagged `chained_declaration` (the `!result.is_empty()`
355359
// test). Must precede the plain `variableDecl` rules.
356360
rule!(
357361
(variableDecl
362+
attributes: _* @attrs
363+
modifiers: _* @mods
358364
bindingSpecifier: @@spec
359365
bindings: (patternBinding
360366
pattern: (identifierPattern identifier: @@name)
@@ -363,8 +369,14 @@ fn translation_rules() -> Vec<Rule<SwiftContext>> {
363369
accessorBlock: (accessorBlock accessors: (accessorDecl)+ @@accessors)))
364370
=>
365371
member* {
366-
ctx.outer_modifiers = vec![tree!((modifier #{spec}))];
372+
// The binding (`let`/`var`) leads, then attributes then modifiers,
373+
// matching the plain `variableDecl` rule.
374+
ctx.outer_modifiers = std::iter::once(tree!((modifier #{spec})))
375+
.chain(attrs)
376+
.chain(mods)
377+
.collect();
367378
ctx.property_name_node = Some(tree!((identifier #{name})));
379+
ctx.property_type = Some(ty);
368380
let mut result = Vec::new();
369381
if let Some(val) = val {
370382
// Stored property with observers: the initializer is not part
@@ -380,9 +392,6 @@ fn translation_rules() -> Vec<Rule<SwiftContext>> {
380392
type: {ty}
381393
value: {val})
382394
));
383-
} else {
384-
// Computed property: the accessors carry the type.
385-
ctx.property_type = Some(ty);
386395
}
387396
for acc in accessors.into_iter() {
388397
ctx.is_chained = !result.is_empty();
@@ -392,35 +401,74 @@ fn translation_rules() -> Vec<Rule<SwiftContext>> {
392401
}
393402
),
394403
// Each `accessorDecl` becomes an `accessor_declaration`, reading the
395-
// property name/type and the binding/chained modifiers from `ctx`. The
404+
// property name/type and the modifiers from `ctx`. The
396405
// accessor kind comes straight from the specifier keyword
397406
// (`get`/`set`/`willSet`/`didSet`). The body is optional: a body-bearing
398407
// accessor (a computed getter/setter, or a `willSet`/`didSet` observer)
399-
// carries the binding modifier and a translated body, whereas a bodyless
400-
// one (a protocol requirement) carries neither. The property context is
408+
// has a translated body, whereas a bodyless one (a protocol requirement)
409+
// has none. The property context is
401410
// read out *before* translating the body, which resets `ctx` so the
402411
// accessor's context does not leak into the body subtree.
403-
rule!(
404-
(accessorDecl accessorSpecifier: @@spec body: _? @@body)
405-
=>
406-
(accessor_declaration
407-
modifier: {
408-
if body.is_some() {
409-
ctx.outer_modifiers.clone()
410-
} else {
411-
Vec::new()
412+
//
413+
// `set` and `willSet` accessors get a parameter holding the incoming
414+
// value, and `didSet` gets one holding the previous value. It is named
415+
// explicitly (`set(v)`) or else implicitly `newValue`/`oldValue`.
416+
rule!(
417+
(accessorDecl
418+
accessorSpecifier: @@spec
419+
parameters: (accessorParameters name: @@param_name)?
420+
body: _? @@body)
421+
=>
422+
accessor_declaration {
423+
let spec_text = ctx.source_text(spec);
424+
let implicit_param_name = match spec_text.as_str() {
425+
"set" | "willSet" => Some("newValue"),
426+
"didSet" => Some("oldValue"),
427+
_ => None,
428+
};
429+
let param_ty = ctx.property_type;
430+
let param = match (param_name, implicit_param_name) {
431+
(Some(n), _) => {
432+
Some(tree!((parameter pattern: (identifier #{n}) type: {param_ty})))
412433
}
413-
}
414-
modifier: {chained_modifier(&mut ctx)}
415-
name_node: {
416-
ctx.property_name_node
417-
.ok_or("accessor outside property context")?
418-
}
419-
type: {ctx.property_type}
420-
accessor_kind: (accessor_kind #{spec})
421-
body: {ctx.reset(); body}
422-
location: {spec}
423-
location: {body})
434+
(None, Some(n)) => {
435+
let n = n.to_string();
436+
Some(tree!((parameter
437+
modifier: (modifier "generated")
438+
pattern: (identifier #{n} location: {spec})
439+
type: {param_ty}
440+
location: {spec})))
441+
}
442+
(None, None) => None,
443+
};
444+
let modifiers = ctx.outer_modifiers.clone();
445+
let chained = chained_modifier(&mut ctx);
446+
let name = ctx
447+
.property_name_node
448+
.ok_or("accessor outside property context")?;
449+
let return_ty = if spec_text == "get" {
450+
ctx.property_type
451+
} else {
452+
None
453+
};
454+
let body = match body {
455+
Some(block) => {
456+
ctx.reset();
457+
ctx.translate(block)?.into_iter().next()
458+
}
459+
None => None,
460+
};
461+
tree!((accessor_declaration
462+
modifier: {modifiers}
463+
modifier: {chained}
464+
name_node: {name}
465+
return_type: {return_ty}
466+
accessor_kind: (accessor_kind #{spec})
467+
parameter: {param}
468+
body: {body}
469+
location: {spec}
470+
location: {body}))
471+
}
424472
),
425473
// ---- Variables ----
426474
// The individual bindings of a `variableDecl`. The binding modifier and

‎unified/extractor/tests/corpus/swift/types/binding-modifier-does-not-leak-into-accessor-body.output‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ top_level source="var p: Int {\n ⟨body⟩\n}"
100100
modifier: modifier "var" source="var" (external)
101101
name_node: identifier "p" source="p" (external)
102102
accessor_kind: accessor_kind "get" source="get"
103-
type: identifier "Int" source="Int" (external)
103+
return_type: identifier "Int" source="Int" (external)
104104
body:
105105
block source="{\n ⟨stmt⟩\n }"
106106
stmt:

‎unified/extractor/tests/corpus/swift/types/computed-property.output‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -113,11 +113,11 @@ top_level source="⟨body⟩"
113113
modifier: modifier "var" source="var"
114114
pattern: identifier "h" source="h"
115115
type: identifier "Double" source="Double"
116-
accessor_declaration source="⟨modifier⟩ ⟨name_node⟩: ⟨type⟩ ⟨body⟩"
116+
accessor_declaration source="⟨modifier⟩ ⟨name_node⟩: ⟨return_type⟩ ⟨body⟩"
117117
modifier: modifier "var" source="var"
118118
name_node: identifier "area" source="area"
119119
accessor_kind: accessor_kind "get" source=""
120-
type: identifier "Double" source="Double"
120+
return_type: identifier "Double" source="Double"
121121
body:
122122
block source="{\n ⟨stmt⟩\n }"
123123
stmt:

‎unified/extractor/tests/corpus/swift/types/property-with-getter-and-setter.output‎

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -120,19 +120,23 @@ top_level source="⟨body⟩"
120120
modifier: modifier "var" source="var" (external)
121121
name_node: identifier "v" source="v" (external)
122122
accessor_kind: accessor_kind "get" source="get"
123-
type: identifier "Int" source="Int" (external)
123+
return_type: identifier "Int" source="Int" (external)
124124
body:
125125
block source="{ ⟨stmt⟩ }"
126126
stmt:
127127
return_expr source="return ⟨value⟩"
128128
value: identifier "_v" source="_v"
129-
accessor_declaration source="⟨accessor_kind⟩ ⟨body⟩"
129+
accessor_declaration source="⟨accessor_kind⟩⟨parameter⟩ ⟨body⟩"
130130
modifier:
131131
modifier "var" source="var" (external)
132132
modifier "chained_declaration" source=""
133133
name_node: identifier "v" source="v" (external)
134134
accessor_kind: accessor_kind "set" source="set"
135-
type: identifier "Int" source="Int" (external)
135+
parameter:
136+
parameter source="⟨pattern⟩"
137+
modifier: modifier "generated" source=""
138+
type: identifier "Int" source="Int" (external)
139+
pattern: identifier "newValue" source="set"
136140
body:
137141
block source="{ ⟨stmt⟩ }"
138142
stmt:

‎unified/extractor/tests/corpus/swift/types/protocol-with-read-only-and-read-write-property-requirements.output‎

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -111,19 +111,28 @@ top_level source="⟨body⟩"
111111
name_node: identifier "P" source="P"
112112
member:
113113
accessor_declaration source="⟨accessor_kind⟩"
114+
modifier: modifier "var" source="var" (external)
114115
name_node: identifier "foo" source="foo" (external)
115116
accessor_kind: accessor_kind "get" source="get"
116-
type: identifier "Int" source="Int" (external)
117+
return_type: identifier "Int" source="Int" (external)
117118
accessor_declaration source="⟨accessor_kind⟩"
119+
modifier: modifier "var" source="var" (external)
118120
name_node: identifier "bar" source="bar" (external)
119121
accessor_kind: accessor_kind "get" source="get"
120-
type: identifier "String" source="String" (external)
121-
accessor_declaration source="⟨accessor_kind⟩"
122-
modifier: modifier "chained_declaration" source=""
122+
return_type: identifier "String" source="String" (external)
123+
accessor_declaration source="⟨accessor_kind⟩⟨parameter⟩"
124+
modifier:
125+
modifier "var" source="var" (external)
126+
modifier "chained_declaration" source=""
123127
name_node: identifier "bar" source="bar" (external)
124128
accessor_kind: accessor_kind "set" source="set"
125-
type: identifier "String" source="String" (external)
129+
parameter:
130+
parameter source="⟨pattern⟩"
131+
modifier: modifier "generated" source=""
132+
type: identifier "String" source="String" (external)
133+
pattern: identifier "newValue" source="set"
126134
accessor_declaration source="⟨accessor_kind⟩"
135+
modifier: modifier "var" source="var" (external)
127136
name_node: identifier "count" source="count" (external)
128137
accessor_kind: accessor_kind "get" source="get"
129-
type: identifier "Int" source="Int" (external)
138+
return_type: identifier "Int" source="Int" (external)

‎unified/extractor/tests/corpus/swift/variables/property-with-willset-and-didset-observers.output‎

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -110,12 +110,17 @@ top_level source="⟨body⟩"
110110
pattern: identifier "x" source="x"
111111
type: identifier "Int" source="Int"
112112
value: int_literal "0" source="0"
113-
accessor_declaration source="⟨accessor_kind⟩ ⟨body⟩"
113+
accessor_declaration source="⟨accessor_kind⟩⟨parameter⟩ ⟨body⟩"
114114
modifier:
115115
modifier "var" source="var" (external)
116116
modifier "chained_declaration" source=""
117117
name_node: identifier "x" source="x" (external)
118118
accessor_kind: accessor_kind "willSet" source="willSet"
119+
parameter:
120+
parameter source="⟨pattern⟩"
121+
modifier: modifier "generated" source=""
122+
type: identifier "Int" source="Int" (external)
123+
pattern: identifier "newValue" source="willSet"
119124
body:
120125
block source="{ ⟨stmt⟩ }"
121126
stmt:
@@ -124,12 +129,17 @@ top_level source="⟨body⟩"
124129
argument:
125130
argument source="⟨value⟩"
126131
value: identifier "newValue" source="newValue"
127-
accessor_declaration source="⟨accessor_kind⟩ ⟨body⟩"
132+
accessor_declaration source="⟨accessor_kind⟩⟨parameter⟩ ⟨body⟩"
128133
modifier:
129134
modifier "var" source="var" (external)
130135
modifier "chained_declaration" source=""
131136
name_node: identifier "x" source="x" (external)
132137
accessor_kind: accessor_kind "didSet" source="didSet"
138+
parameter:
139+
parameter source="⟨pattern⟩"
140+
modifier: modifier "generated" source=""
141+
type: identifier "Int" source="Int" (external)
142+
pattern: identifier "oldValue" source="didSet"
133143
body:
134144
block source="{ ⟨stmt⟩ }"
135145
stmt:

‎unified/ql/consistency-queries/DataFlowConsistency.ql‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@ private import codeql.dataflow.internal.DataFlowImplConsistency
55
module ConsistencyInput implements InputSig<Location, DataFlowInput> {
66
predicate argHasPostUpdateExclude(DataFlowInput::ArgumentNode n) {
77
not exists(n.getBasicBlock()) // ignore unreachable data flow nodes
8+
or
9+
// The value passed into a setter call technically needs a post-update node
10+
// because it's an argument to a call, but this rarely matters in practice.
11+
n.(BuilderNode).isSetterValueArgument(_)
812
}
913

1014
predicate reverseReadExclude(DataFlow::Node n) {

‎unified/ql/lib/codeql/unified/internal/Ast.qll‎

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -123,8 +123,10 @@ module Unified {
123123
/** Gets the node corresponding to the field `parameter`. */
124124
final override F::Parameter getAParameter() { result = this.getParameter(_) }
125125

126-
/** Gets the node corresponding to the field `type`. */
127-
final F::Expr getType() { unified_accessor_declaration_type(this, result) }
126+
/** Gets the node corresponding to the field `return_type`. */
127+
final override F::Expr getReturnType() {
128+
unified_accessor_declaration_return_type(this, result)
129+
}
128130

129131
/** Gets a field or child node of this node. */
130132
final override F::AstNode getAFieldOrChild() {
@@ -133,7 +135,7 @@ module Unified {
133135
unified_accessor_declaration_modifier(this, _, result) or
134136
unified_accessor_declaration_def(this, _, result) or
135137
unified_accessor_declaration_parameter(this, _, result) or
136-
unified_accessor_declaration_type(this, result)
138+
unified_accessor_declaration_return_type(this, result)
137139
}
138140
}
139141

@@ -1624,7 +1626,7 @@ module Unified {
16241626
or
16251627
result = node.(AccessorDeclaration).getParameter(i) and name = "getParameter"
16261628
or
1627-
result = node.(AccessorDeclaration).getType() and i = -1 and name = "getType"
1629+
result = node.(AccessorDeclaration).getReturnType() and i = -1 and name = "getReturnType"
16281630
or
16291631
result = node.(Argument).getModifier(i) and name = "getModifier"
16301632
or

‎unified/ql/lib/codeql/unified/internal/ExprPositions.qll‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ predicate isInTypeContext(Expr expr) {
1616
or
1717
expr = any(FunctionExpr n).getReturnType()
1818
or
19-
expr = any(AccessorDeclaration n).getType()
19+
expr = any(AccessorDeclaration n).getReturnType()
2020
or
2121
expr = any(Parameter n).getType()
2222
or

0 commit comments

Comments
 (0)