Skip to content

Commit 5b468f2

Browse files
committed
Unified: Resolve accessor accesses in type inference
1 parent 9fd04e6 commit 5b468f2

12 files changed

Lines changed: 609 additions & 76 deletions

File tree

‎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: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,12 @@ private class SwiftDataFlowPlugin extends DataFlowPlugin {
1313
contents.asSingleton().asNamedMember() = "some.0"
1414
}
1515

16-
// Swift writes back mutated values of computed properties via their setter
16+
// Swift writes back mutated values of computed value-type properties via their setter
1717
bindingset[expr]
1818
override predicate mayWriteBackViaSetterCall(Expr expr) {
19-
isInWriteBackPosition(expr)
20-
// TODO: Check that 'expr' has a value type. Reference types don't cause writebacks.
19+
isInWriteBackPosition(expr) and
20+
// conservative value-type check
21+
not forex(T::Type t | t = T::inferType(expr) | t.asClass().hasModifier("class"))
2122
}
2223

2324
override predicate exprStep(Expr expr1, Expr expr2, boolean mustFlow) {

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

Lines changed: 69 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) {
@@ -835,7 +871,23 @@ module CachedStage = M3::CachedStage;
835871
cached
836872
VariableDeclaration resolveFieldAccess(Expr access) {
837873
CachedStage::ref() and
838-
result = access.(Input3::FieldAccess).getField().getDecl()
874+
result = access.(Input3::FieldAccess).getField().getAstNode()
875+
}
876+
877+
/**
878+
* Gets the accessor that is accessed by the given expression.
879+
*
880+
* Note that we do not attempt to distinguish between different kinds of
881+
* accessors; all accessors belonging to the same declaration will be
882+
* returned (for example both the getter and setter of a property).
883+
*
884+
* `access` is either a `MemberAccessExpr` or an `Identifier` (when
885+
* the field is accessed with an implicit receiver).
886+
*/
887+
cached
888+
AccessorDeclaration resolveAccessorAccess(Expr access) {
889+
CachedStage::ref() and
890+
result = access.(Input3::FieldAccess).getField().getAstNode()
839891
}
840892

841893
/**

‎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: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -131,11 +131,11 @@ func t11() {
131131
class C {
132132
var p: (String, String) {
133133
get { ("safe", "safe") }
134-
set { sink(newValue.0) } // $ hasValueFlow=t11.1 $ MISSING: hasValueFlow=t11.2
134+
set { sink(newValue.0) } // $ hasValueFlow=t11.1 hasValueFlow=t11.2
135135
}
136136
var q: C {
137137
get { C() }
138-
set { sink(newValue.p.0) } // $ SPURIOUS: hasValueFlow=t11.2
138+
set { sink(newValue.p.0) }
139139
}
140140
}
141141
let c = C()
@@ -151,9 +151,9 @@ func t12() {
151151
class C {
152152
var s: S {
153153
get { S() }
154-
set { sink(newValue.field) } // $ MISSING: hasValueFlow=t12.1
154+
set { sink(newValue.field) } // $ hasValueFlow=t12.1
155155
}
156156
}
157157
let c = C()
158-
c.s.update() // MISSING: the call to 'update' is not resolved
158+
c.s.update()
159159
}

‎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)