Skip to content

Commit baa1d3a

Browse files
authored
Merge pull request #22909 from hvitved/unified/type-inference-resolve-accessors
Unified: Resolve accessor accesses in type inference
2 parents 835afbb + 6263dc9 commit baa1d3a

13 files changed

Lines changed: 656 additions & 79 deletions

File tree

‎shared/typeinference/codeql/typeinference/internal/TypeInference.qll‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3683,6 +3683,16 @@ module Make1<LocationSig Location, InputSig1<Location> Input1> {
36833683
pragma[nomagic]
36843684
Type inferType(AstNode n) { result = inferType(n, TypePath::nil()) }
36853685

3686+
/** Gets the unique non-pseudo type inferred at `path` for `n`, if any. */
3687+
Type inferTypeUnique(AstNode n, TypePath path) {
3688+
result = unique(Type t | t = inferType(n, path) and not t instanceof PseudoType | t)
3689+
}
3690+
3691+
/**
3692+
* Gets the unique non-pseudo type inferred at the root for `n`, if any.
3693+
*/
3694+
Type inferTypeUnique(AstNode n) { result = inferTypeUnique(n, TypePath::nil()) }
3695+
36863696
/**
36873697
* The cached stage of this module.
36883698
*

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

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -88,10 +88,16 @@ class NameBindingPluginSwift extends NameBindingPlugin {
8888
or
8989
not m instanceof GeneratedConstructor and
9090
result =
91-
concat(int i, Parameter p |
92-
p = m.(Callable).getParameter(i)
91+
concat(int i, Parameter p, string name |
92+
p = m.(Callable).getParameter(i) and
93+
(
94+
name = p.getExternalNameNode().getValue()
95+
or
96+
not exists(p.getExternalNameNode()) and
97+
name = "_"
98+
)
9399
|
94-
p.getExternalNameNode().getValue(), "," order by i
100+
name, "," order by i
95101
)
96102
}
97103

‎unified/ql/lib/codeql/unified/internal/dataflow/CallGraph.qll‎

Lines changed: 2 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -7,26 +7,9 @@ private import codeql.unified.internal.ExprPositions
77
DataFlowCallable viableCallable(DataFlowCall c) {
88
result.asSourceCallable() = T::resolveCallTarget(c.asExplicitCall())
99
or
10-
result.asSourceCallable() = resolveAccessor(c.asGetterCall()).(GetterDeclaration)
10+
result.asSourceCallable() = T::resolveAccessorAccess(c.asGetterCall()).(GetterDeclaration)
1111
or
12-
result.asSourceCallable() = resolveAccessor(c.asSetterCall()).(SetterDeclaration)
12+
result.asSourceCallable() = T::resolveAccessorAccess(c.asSetterCall()).(SetterDeclaration)
1313
or
1414
result.asSummarizedCallable().matchesCall(c.asExplicitCall())
1515
}
16-
17-
bindingset[access]
18-
pragma[inline_late]
19-
private N::NamespaceNode resolveBase(MemberAccessExpr access) {
20-
result.isInstanceMemberNamespace(T::inferType(access.getBase()).asClass())
21-
}
22-
23-
private AccessorDeclaration resolveAccessor(Expr access) {
24-
result.getNameNode() = N::getStaticBindingTargetFromRef(access)
25-
or
26-
not exists(N::getStaticBindingTargetFromRef(access)) and
27-
exists(MemberAccessExpr m |
28-
access = m and
29-
// TODO: Make type inference own accessor-resolution. It also needs it internally.
30-
resolveBase(m).getMember(m.getMemberName()).isMember(result)
31-
)
32-
}

‎unified/ql/lib/codeql/unified/internal/dataflow/DataFlowCall.qll‎

Lines changed: 3 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,15 @@ private import unified
22
private import AllDataFlow
33
private import codeql.unified.internal.ExprPositions
44
private import codeql.unified.internal.NameBinding as N
5+
private import codeql.unified.internal.typeinference.TypeInference as T
56

67
private newtype TDataFlowCall =
78
TExplicitCall(CallExpr call) {
89
not isInBindingContext(call, _) // ignore constructor patterns
910
} or
1011
TGetterCall(Expr expr) {
1112
hasResultValue(expr) and
12-
(
13-
N::getStaticBindingTargetFromRef(expr) = any(GetterDeclaration g).getNameNode()
14-
or
15-
// Assume a qualified member accesses might resolve to a getter.
16-
expr instanceof MemberAccessExpr
17-
)
13+
T::resolveAccessorAccess(expr) instanceof GetterDeclaration
1814
} or
1915
TSetterCall(Expr expr) {
2016
(
@@ -24,12 +20,7 @@ private newtype TDataFlowCall =
2420
hasPostUpdateAfterCallOrAssign(expr, _) and
2521
mayWriteBackViaSetterCall(expr)
2622
) and
27-
(
28-
N::getStaticBindingTargetFromRef(expr) = any(SetterDeclaration g).getNameNode()
29-
or
30-
// Assume a qualified member accesses might resolve to a setter.
31-
expr instanceof MemberAccessExpr
32-
)
23+
T::resolveAccessorAccess(expr) instanceof SetterDeclaration
3324
} or
3425
TSummaryCall(SummarizedCallable callable, SummaryNode callee) {
3526
FlowSummaryOutput::summaryCallbackRange(callable, callee)

‎unified/ql/lib/codeql/unified/internal/dataflow/DataFlowPluginSwift.qll‎

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,17 +7,23 @@ private import AllDataFlow
77
private import codeql.unified.internal.ExprPositions
88
private import codeql.unified.internal.typeinference.TypeInference as T
99

10+
private predicate isReferenceType(T::Type t) {
11+
// todo: type parameters with type constraints
12+
t.asClass().hasModifier(["actor", "class"])
13+
}
14+
1015
private class SwiftDataFlowPlugin extends DataFlowPlugin {
1116
// Note: For now we assume all code is Swift, but in the future we must restrict these rules to Swift-files
1217
override predicate bypassContentSet(ContentSet contents) {
1318
contents.asSingleton().asNamedMember() = "some.0"
1419
}
1520

16-
// Swift writes back mutated values of computed properties via their setter
21+
// Swift writes back mutated values of computed value-type properties via their setter
1722
bindingset[expr]
1823
override predicate mayWriteBackViaSetterCall(Expr expr) {
19-
isInWriteBackPosition(expr)
20-
// TODO: Check that 'expr' has a value type. Reference types don't cause writebacks.
24+
exists(boolean direct | isInWriteBackPosition(expr, direct) |
25+
if direct = false then not isReferenceType(T::inferTypeUnique(expr)) else any()
26+
)
2127
}
2228

2329
override predicate exprStep(Expr expr1, Expr expr2, boolean mustFlow) {
@@ -100,25 +106,29 @@ private class SwiftDataFlowPlugin extends DataFlowPlugin {
100106
* Holds if `expr` is target of an assignment or 'inout' argument,
101107
* or the base of such an expression, e.g. `x` in `x.f1.f2 = foo()`
102108
*/
103-
private predicate isInWriteBackPosition(Expr expr) {
104-
isInAssignmentContext(expr, _)
109+
private predicate isInWriteBackPosition(Expr expr, boolean direct) {
110+
isInAssignmentContext(expr, _) and
111+
direct = false
105112
or
106113
// Arguments to an 'inout' parameter must be prefixed with '&'
107114
exists(UnaryExpr unary |
108115
unary.getOperator().(PrefixOperator).getValue() = "&" and
109116
unary.getOperand() = expr
110117
// TODO: support custom operators with an 'inout' parameter, which lack the '&' indicator
111-
)
118+
) and
119+
direct = true
112120
or
113121
// The receiver of a call to a 'mutating' function counts as an 'inout' argument
114122
exists(CallExpr call, Callable target |
115123
target = T::resolveCallTarget(call) and
116124
target.hasModifier("mutating") and
117125
expr = call.getCallee().(MemberAccessExpr).getBase()
118-
)
126+
) and
127+
direct = true
119128
or
120129
exists(MemberAccessExpr member |
121-
isInWriteBackPosition(member) and
122-
expr = member.getBase()
130+
isInWriteBackPosition(member, _) and
131+
expr = member.getBase() and
132+
direct = false
123133
)
124134
}

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

Lines changed: 73 additions & 17 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,20 +254,38 @@ 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.getReturnType() }
285+
286+
override AstNode getAstNode() { result = accessor }
287+
}
288+
269289
abstract class FieldAccess extends Expr {
270290
abstract Expr getReceiver();
271291

@@ -275,7 +295,7 @@ module Input3 implements InputSig3 {
275295
pragma[nomagic]
276296
private predicate lookupMember0(MemberAccessExpr mae, ClassLikeDeclaration cls, string name) {
277297
not exists(getStaticBindingTargetFromRef(mae)) and // if a static member can be resolved, use it
278-
cls = inferType(mae.getBase()).(ClassLikeDeclarationType).getClassLikeDeclaration() and
298+
cls = inferType(mae.getBase()).asClass() and
279299
name = mae.getMemberName()
280300
}
281301

@@ -303,7 +323,7 @@ module Input3 implements InputSig3 {
303323
// also mutual recursion
304324
exists(TupleType tuple, string name |
305325
this.isTupleProjection(tuple, name) and
306-
result.getDecl() = tuple.getField(name)
326+
result.getAstNode() = tuple.getField(name)
307327
)
308328
or
309329
// no mutual recursion; can be resolved directly with static name binding
@@ -325,6 +345,18 @@ module Input3 implements InputSig3 {
325345
override Field getField() { result.getNameNode() = this.getTarget() }
326346
}
327347

348+
private class LocalAccessorFieldAccess extends FieldAccess, LocalNameAccess {
349+
AccessorDeclaration target;
350+
351+
LocalAccessorFieldAccess() {
352+
target = getStaticBindingTargetFromIdentifier(this).getDeclaration()
353+
}
354+
355+
override Expr getReceiver() { none() }
356+
357+
override Field getField() { result.getAstNode() = target }
358+
}
359+
328360
Type inferFieldAccessReceiverType(FieldAccess fa, TypePath path) {
329361
// instance field with explicit receiver
330362
result = inferType(fa.getReceiver(), path)
@@ -506,17 +538,21 @@ module Input3 implements InputSig3 {
506538
}
507539

508540
Callable getTargetImpl(boolean isFunctionExprInvoke) {
509-
// mutual recursion; call with explicit receiver: `obj.foo(42)`
510-
result = lookupInstanceMember(this.getCallee()) and
511-
isFunctionExprInvoke = false
512-
or
513-
// mutual recursion; call to a function expression: `callback(42)`
514-
result.asCallable() = this.getInvokeTarget(_) and
515-
isFunctionExprInvoke = true
516-
or
517-
// no mutual recursion; can be resolved directly with static name binding
518-
result = this.getTargetViaStaticNameBinding() and
519-
isFunctionExprInvoke = false
541+
(
542+
// mutual recursion; call with explicit receiver: `obj.foo(42)`
543+
result = lookupInstanceMember(this.getCallee()) and
544+
isFunctionExprInvoke = false
545+
or
546+
// mutual recursion; call to a function expression: `callback(42)`
547+
result.asCallable() = this.getInvokeTarget(_) and
548+
isFunctionExprInvoke = true
549+
or
550+
// no mutual recursion; can be resolved directly with static name binding
551+
result = this.getTargetViaStaticNameBinding() and
552+
isFunctionExprInvoke = false
553+
) and
554+
// handled as a field access
555+
not result.asCallable() instanceof AccessorDeclaration
520556
}
521557

522558
Callable getTarget(InvocationResolutionContext c) {
@@ -820,6 +856,10 @@ predicate inferType = M3::inferType/1;
820856

821857
predicate inferType = M3::inferType/2;
822858

859+
predicate inferTypeUnique = M3::inferTypeUnique/1;
860+
861+
predicate inferTypeUnique = M3::inferTypeUnique/2;
862+
823863
predicate inferTypeCertain = M3::inferTypeCertain/2;
824864

825865
module Consistency = M3::Consistency;
@@ -835,7 +875,23 @@ module CachedStage = M3::CachedStage;
835875
cached
836876
VariableDeclaration resolveFieldAccess(Expr access) {
837877
CachedStage::ref() and
838-
result = access.(Input3::FieldAccess).getField().getDecl()
878+
result = access.(Input3::FieldAccess).getField().getAstNode()
879+
}
880+
881+
/**
882+
* Gets the accessor that is accessed by the given expression.
883+
*
884+
* Note that we do not attempt to distinguish between different kinds of
885+
* accessors; all accessors belonging to the same declaration will be
886+
* returned (for example both the getter and setter of a property).
887+
*
888+
* `access` is either a `MemberAccessExpr` or an `Identifier` (when
889+
* the field is accessed with an implicit receiver).
890+
*/
891+
cached
892+
AccessorDeclaration resolveAccessorAccess(Expr access) {
893+
CachedStage::ref() and
894+
result = access.(Input3::FieldAccess).getField().getAstNode()
839895
}
840896

841897
/**

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

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,11 @@ abstract private class TypeMentionImpl extends AstNode {
2424
final class ExprTypeMention = ExprTypeMentionImpl;
2525

2626
abstract private class ExprTypeMentionImpl extends TypeMentionImpl, Expr {
27-
ExprTypeMentionImpl() { isInTypeContext(this) and not this instanceof GenericTypeExpr }
27+
ExprTypeMentionImpl() {
28+
isInTypeContext(this) and
29+
not this instanceof GenericTypeExpr and
30+
not this instanceof ModifiedTypeExpr
31+
}
2832

2933
pragma[nomagic]
3034
abstract TypePath getTypeArgumentPath(int i);
@@ -191,6 +195,14 @@ private class ClassLikeDeclarationTypeMention extends TypeMentionImpl, Identifie
191195
}
192196
}
193197

198+
private class ModifiedTypeExprMention extends TypeMentionImpl instanceof ModifiedTypeExpr {
199+
private TypeMention base;
200+
201+
ModifiedTypeExprMention() { base = ModifiedTypeExpr.super.getType() }
202+
203+
override Type getTypeAt(TypePath path) { result = base.getTypeAt(path) }
204+
}
205+
194206
TypeMention getClassLikeDeclarationTypeMention(ClassLikeDeclaration c) {
195207
result = c.getNameNode()
196208
or

‎unified/ql/test/library-tests/dataflow/accessors_instance.swift‎

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -131,11 +131,18 @@ func t11() {
131131
class C {
132132
var p: (String, String) {
133133
get { ("safe", "safe") }
134-
set { sink(newValue.0) } // $ hasValueFlow=t11.1
134+
set { sink(newValue.0) } // $ hasValueFlow=t11.1 hasValueFlow=t11.2
135+
}
136+
var q: C {
137+
get { C() }
138+
set { sink(newValue.p.0) }
135139
}
136140
}
137141
let c = C()
142+
// `p` has value type, so this calls `p`'s setter
138143
c.p.0 = source("t11.1")
144+
// `q` has reference type, so this calls `q`'s getter followed by `p`'s setter
145+
c.q.p = (source("t11.2"), source("t11.3"))
139146
}
140147

141148
func t12() {
@@ -146,9 +153,9 @@ func t12() {
146153
class C {
147154
var s: S {
148155
get { S() }
149-
set { sink(newValue.field) } // $ MISSING: hasValueFlow=t12.1
156+
set { sink(newValue.field) } // $ hasValueFlow=t12.1
150157
}
151158
}
152159
let c = C()
153-
c.s.update() // MISSING: the call to 'update' is not resolved
160+
c.s.update()
154161
}

‎unified/ql/test/library-tests/dataflow/accessors_local.swift‎

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -100,9 +100,7 @@ func t12() {
100100
}
101101
var p: S {
102102
get { S() }
103-
set { sink(newValue.field) } // $ MISSING: hasValueFlow=t12.1
103+
set { sink(newValue.field) } // $ hasValueFlow=t12.1
104104
}
105-
// TODO: test fails because the call to 'mutate' is not yet resolved
106-
// At the time of writing, the test passes if the call graph is manually patched
107105
p.mutate()
108106
}

0 commit comments

Comments
 (0)