Skip to content

Commit 06918fe

Browse files
committed
Unified: Resolve accessor accesses in type inference
1 parent 1bc4e40 commit 06918fe

8 files changed

Lines changed: 393 additions & 14 deletions

File tree

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -451,6 +451,8 @@ module Unified {
451451
*/
452452
final class GetterDeclaration extends AccessorDeclaration {
453453
GetterDeclaration() { this.getAccessorKind().getValue() = "get" }
454+
455+
override string getNameOrFallback() { result = "get_" + this.getNameNode().getValue() }
454456
}
455457

456458
/**
@@ -466,6 +468,8 @@ module Unified {
466468
*/
467469
final class SetterDeclaration extends AccessorDeclaration {
468470
SetterDeclaration() { this.getAccessorKind().getValue() = "set" }
471+
472+
override string getNameOrFallback() { result = "set_" + this.getNameNode().getValue() }
469473
}
470474

471475
/** An `if` statement or conditional expression. */

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

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,33 @@ private module LocalNameBindingInput implements LocalNameBindingInputSig<Locatio
179179
any(NameBindingPlugin p).isNonPattern(e)
180180
}
181181

182+
pragma[nomagic]
183+
private predicate accessorInScope(AstNode scope, string name, AccessorDeclaration accessor) {
184+
getChild(scope, _) = accessor and
185+
name = accessor.getName()
186+
}
187+
188+
pragma[nomagic]
189+
private predicate variableDeclarationInScope(AstNode scope, string name) {
190+
exists(VariableDeclaration decl |
191+
getChild(scope, _) = decl and
192+
name = decl.getName()
193+
)
194+
}
195+
196+
/**
197+
* Holds if the given accessor has a backing field with the same name.
198+
*
199+
* In this case we resolve all accesses to the field instead of any of the
200+
* accessors using the field.
201+
*/
202+
additional predicate hasBackingField(AccessorDeclaration accessor) {
203+
exists(AstNode scope, string name |
204+
accessorInScope(scope, name, accessor) and
205+
variableDeclarationInScope(scope, name)
206+
)
207+
}
208+
182209
cached
183210
additional predicate bindingContext(AstNode pattern, AstNode scope, AstNode declaration) {
184211
LocalNameBindingOutput::CachedStage::ref() and
@@ -253,6 +280,7 @@ private module LocalNameBindingInput implements LocalNameBindingInputSig<Locatio
253280
)
254281
or
255282
exists(AccessorDeclaration decl |
283+
not hasBackingField(decl) and
256284
getChild(scope, _) = decl and
257285
pattern = decl.getNameNode() and
258286
declaration = decl
@@ -474,6 +502,10 @@ module Public {
474502
* ```
475503
*/
476504
class PotentialLocalNameAccess extends IdentifierExpr {
505+
PotentialLocalNameAccess() {
506+
not this = any(AccessorDeclaration a | LocalNameBindingInput::hasBackingField(a)).getNameNode()
507+
}
508+
477509
/** Gets the representative for the local name being accessed. */
478510
LocalName getLocalName() { result = this.(LocalNameBindingOutput::LocalAccess).getLocal() }
479511

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,8 @@ predicate isInstanceMember(Member member) {
8181
(
8282
member instanceof VariableDeclaration or
8383
member instanceof FunctionDeclaration or
84-
member instanceof ConstructorDeclaration
84+
member instanceof ConstructorDeclaration or
85+
member instanceof AccessorDeclaration
8586
) and
8687
exists(ClassLikeDeclaration cls | cls.getAMember() = member |
8788
any(NameBindingPlugin p).isInstanceMember(cls, member)

‎unified/ql/lib/codeql/unified/internal/typeinference/TypeInference.qll‎

Lines changed: 53 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,8 @@ module Input3 implements InputSig3 {
123123
predicate cacheRevRef() {
124124
(exists(resolveFieldAccess(_)) implies any())
125125
or
126+
(exists(resolveAccessorAccess(_)) implies any())
127+
or
126128
(exists(resolveCallTarget(_)) implies any())
127129
}
128130

@@ -252,24 +254,55 @@ module Input3 implements InputSig3 {
252254
override Location getLocation() { result = decl.getLocation() }
253255
}
254256

255-
class Field extends AstVariableDeclaration {
257+
final class Field = FieldImpl;
258+
259+
abstract private class FieldImpl extends DeclarationImpl {
260+
abstract AstNode getAstNode();
261+
}
262+
263+
private class VariableDeclarationField extends FieldImpl, AstVariableDeclaration {
256264
ClassLikeDeclaration cls;
257265

258-
Field() { decl = cls.getAMember() }
266+
VariableDeclarationField() { decl = cls.getAMember() }
267+
268+
override AstNode getAstNode() { result = this.getDecl() }
259269

260270
override TypeMention getDeclaringType() { result = getClassLikeDeclarationTypeMention(cls) }
261271
}
262272

263-
private class EnumField_ extends Field {
273+
private class EnumField_ extends VariableDeclarationField {
264274
override EnumField decl;
265275

266276
override TypeMention getType() { result = decl.getEnum().getNameNode() }
267277
}
268278

279+
private class AccessorField extends FieldImpl, Callable {
280+
private AccessorDeclaration accessor;
281+
282+
AccessorField() { accessor = this.getCallable() }
283+
284+
override TypeMention getType() { result = accessor.getType() }
285+
286+
override AstNode getAstNode() { result = accessor }
287+
}
288+
269289
abstract class FieldAccess extends Expr {
270290
abstract Expr getReceiver();
271291

272-
abstract Field getField();
292+
abstract Field getFieldImpl();
293+
294+
Field getField() {
295+
result = this.getFieldImpl() and
296+
(
297+
result instanceof VariableDeclaration
298+
or
299+
hasIncomingValue(this, _) and
300+
result.getAstNode() instanceof SetterDeclaration
301+
or
302+
hasResultValue(this) and
303+
result.getAstNode() instanceof GetterDeclaration
304+
)
305+
}
273306
}
274307

275308
pragma[nomagic]
@@ -296,14 +329,14 @@ module Input3 implements InputSig3 {
296329
name = this.getMemberName()
297330
}
298331

299-
override Field getField() {
332+
override Field getFieldImpl() {
300333
// mutual recursion; resolving qualified fields requires resolving types and vice versa
301334
result = lookupInstanceMember(this)
302335
or
303336
// also mutual recursion
304337
exists(TupleType tuple, string name |
305338
this.isTupleProjection(tuple, name) and
306-
result.getDecl() = tuple.getField(name)
339+
result.getAstNode() = tuple.getField(name)
307340
)
308341
or
309342
// no mutual recursion; can be resolved directly with static name binding
@@ -322,7 +355,7 @@ module Input3 implements InputSig3 {
322355
private class UnqualifiedMemberAccessFieldAccess extends FieldAccess, UnqualifiedMemberAccess {
323356
override Expr getReceiver() { none() }
324357

325-
override Field getField() { result.getNameNode() = this.getTarget() }
358+
override Field getFieldImpl() { result.getNameNode() = this.getTarget() }
326359
}
327360

328361
Type inferFieldAccessReceiverType(FieldAccess fa, TypePath path) {
@@ -835,7 +868,19 @@ module CachedStage = M3::CachedStage;
835868
cached
836869
VariableDeclaration resolveFieldAccess(Expr access) {
837870
CachedStage::ref() and
838-
result = access.(Input3::FieldAccess).getField().getDecl()
871+
result = access.(Input3::FieldAccess).getField().getAstNode()
872+
}
873+
874+
/**
875+
* Gets the accessor that is accessed by the given expression.
876+
*
877+
* `access` is either a `MemberAccessExpr` or an `Identifier` (when
878+
* the field is accessed with an implicit receiver).
879+
*/
880+
cached
881+
AccessorDeclaration resolveAccessorAccess(Expr access) {
882+
CachedStage::ref() and
883+
result = access.(Input3::FieldAccess).getField().getAstNode()
839884
}
840885

841886
/**

‎unified/ql/lib/codeql/unified/internal/typeinference/TypeMention.qll‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,14 @@ private class ClassLikeDeclarationTypeMention extends TypeMentionImpl, Identifie
191191
}
192192
}
193193

194+
private class ModifiedTypeExprMention extends TypeMentionImpl instanceof ModifiedTypeExpr {
195+
private TypeMention base;
196+
197+
ModifiedTypeExprMention() { base = ModifiedTypeExpr.super.getType() }
198+
199+
override Type getTypeAt(TypePath path) { result = base.getTypeAt(path) }
200+
}
201+
194202
TypeMention getClassLikeDeclarationTypeMention(ClassLikeDeclaration c) {
195203
result = c.getNameNode()
196204
or
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
// --- Computed properties ---
2+
3+
class AccessorContainer {
4+
var stored: Int = 0
5+
6+
var implicitGetter: Int {
7+
return stored // $ type=stored:Int field=AccessorContainer.stored
8+
}
9+
10+
var explicitGetter: String {
11+
let value = "accessor" // $ type=value:String
12+
return value // $ type=value:String
13+
}
14+
15+
var writable: Int {
16+
get {
17+
return stored // $ type=stored:Int field=AccessorContainer.stored
18+
}
19+
set {
20+
stored = newValue // $ field=AccessorContainer.stored MISSING: type=newValue:Int
21+
}
22+
}
23+
24+
var namedSetter: Int {
25+
get {
26+
return stored // $ type=stored:Int field=AccessorContainer.stored
27+
}
28+
set(value) {
29+
stored = value // $ field=AccessorContainer.stored MISSING: type=value:Int
30+
}
31+
}
32+
}
33+
34+
func testComputedProperties() {
35+
let container = AccessorContainer() // $ target=AccessorContainer.init
36+
let implicitValue = container.implicitGetter // $ type=implicitValue:Int accessor=AccessorContainer.get_implicitGetter
37+
let explicitValue = container.explicitGetter // $ type=explicitValue:String accessor=AccessorContainer.get_explicitGetter
38+
let writableValue = container.writable // $ type=writableValue:Int accessor=AccessorContainer.get_writable
39+
container.writable = 42 // $ accessor=AccessorContainer.set_writable
40+
let namedValue = container.namedSetter // $ type=namedValue:Int accessor=AccessorContainer.get_namedSetter
41+
container.namedSetter = 43 // $ accessor=AccessorContainer.set_namedSetter
42+
}
43+
44+
// --- Stored properties with observers ---
45+
46+
class ObservedProperties {
47+
var observed: Int = 0 {
48+
willSet {
49+
let current = observed // $ type=current:Int field=ObservedProperties.observed
50+
let incoming = newValue // $ MISSING: type=incoming:Int
51+
}
52+
didSet {
53+
let current = observed // $ type=current:Int field=ObservedProperties.observed
54+
let previous = oldValue // $ MISSING: type=previous:Int
55+
}
56+
}
57+
58+
var namedObservers: String = "" {
59+
willSet(incoming) {
60+
let value = incoming // $ MISSING: type=value:String
61+
}
62+
didSet(previous) {
63+
let value = previous // $ MISSING: type=value:String
64+
}
65+
}
66+
}
67+
68+
func testObservedProperties() {
69+
let container = ObservedProperties() // $ target=ObservedProperties.init
70+
let observed = container.observed // $ type=observed:Int field=ObservedProperties.observed
71+
container.observed = 42 // $ field=ObservedProperties.observed
72+
let named = container.namedObservers // $ type=named:String field=ObservedProperties.namedObservers
73+
container.namedObservers = "updated" // $ field=ObservedProperties.namedObservers
74+
}
75+
76+
// --- Generic and inherited accessors ---
77+
78+
class GenericAccessors<T> {
79+
var stored: T
80+
81+
init(value: T) {
82+
stored = value // $ type=value:T field=GenericAccessors.stored
83+
}
84+
85+
var value: T {
86+
get {
87+
return stored // $ type=stored:T field=GenericAccessors.stored
88+
}
89+
set {
90+
stored = newValue // $ field=GenericAccessors.stored MISSING: type=newValue:T
91+
}
92+
}
93+
}
94+
95+
class InheritedAccessors: GenericAccessors<Int> {}
96+
97+
extension GenericAccessors {
98+
var extensionValue: T {
99+
return stored // $ field=GenericAccessors.stored $ MISSING: type=stored:T
100+
}
101+
}
102+
103+
func testGenericAccessors() {
104+
let container = GenericAccessors(value: "value") // $ target=GenericAccessors.init
105+
let value = container.value // $ type=value:String accessor=GenericAccessors.get_value
106+
let extensionValue = container.extensionValue // $ accessor=GenericAccessors.get_extensionValue $ MISSING: type=extensionValue:String
107+
let inherited = InheritedAccessors(value: 42) // $ target=GenericAccessors.init
108+
let inheritedValue = inherited.value // $ type=inheritedValue:Int accessor=GenericAccessors.get_value
109+
}
110+
111+
// --- Protocol accessor requirements ---
112+
113+
protocol AccessorRequirements {
114+
var readOnly: Int { get }
115+
var readWrite: String { get set }
116+
}
117+
118+
func testAccessorRequirements(_ value: inout AccessorRequirements) {
119+
let readOnly = value.readOnly // $ type=readOnly:Int accessor=AccessorRequirements.get_readOnly
120+
let readWrite = value.readWrite // $ type=readWrite:String accessor=AccessorRequirements.get_readWrite
121+
value.readWrite = "updated" // $ accessor=AccessorRequirements.set_readWrite
122+
}

0 commit comments

Comments
 (0)