Skip to content

Commit 06e5962

Browse files
authored
Merge pull request #8791 from asgerf/js/static-accessors
Approved by erik-krogh
2 parents 789b0a4 + c6e66ed commit 06e5962

File tree

11 files changed

+244
-34
lines changed

11 files changed

+244
-34
lines changed

javascript/ql/lib/semmle/javascript/dataflow/DataFlow.qll

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -589,6 +589,13 @@ module DataFlow {
589589
* Gets the node where the property write happens in the control flow graph.
590590
*/
591591
abstract ControlFlowNode getWriteNode();
592+
593+
/**
594+
* If this installs an accessor on an object, as opposed to a regular property,
595+
* gets the body of the accessor. `isSetter` is true if installing a setter, and
596+
* false is installing a getter.
597+
*/
598+
DataFlow::FunctionNode getInstalledAccessor(boolean isSetter) { none() }
592599
}
593600

594601
/**
@@ -628,6 +635,17 @@ module DataFlow {
628635

629636
override Node getRhs() { result = valueNode(prop.(ValueProperty).getInit()) }
630637

638+
override DataFlow::FunctionNode getInstalledAccessor(boolean isSetter) {
639+
(
640+
prop instanceof PropertySetter and
641+
isSetter = true
642+
or
643+
prop instanceof PropertyGetter and
644+
isSetter = false
645+
) and
646+
result = valueNode(prop.getInit())
647+
}
648+
631649
override ControlFlowNode getWriteNode() { result = prop }
632650
}
633651

@@ -688,6 +706,17 @@ module DataFlow {
688706
result = valueNode(prop.getInit())
689707
}
690708

709+
override DataFlow::FunctionNode getInstalledAccessor(boolean isSetter) {
710+
(
711+
prop instanceof SetterMethodDefinition and
712+
isSetter = true
713+
or
714+
prop instanceof GetterMethodDefinition and
715+
isSetter = false
716+
) and
717+
result = valueNode(prop.getInit())
718+
}
719+
691720
override ControlFlowNode getWriteNode() { result = prop }
692721
}
693722

@@ -711,6 +740,17 @@ module DataFlow {
711740
result = valueNode(prop.getInit())
712741
}
713742

743+
override DataFlow::FunctionNode getInstalledAccessor(boolean isSetter) {
744+
(
745+
prop instanceof SetterMethodDefinition and
746+
isSetter = true
747+
or
748+
prop instanceof GetterMethodDefinition and
749+
isSetter = false
750+
) and
751+
result = valueNode(prop.getInit())
752+
}
753+
714754
override ControlFlowNode getWriteNode() { result = prop }
715755
}
716756

javascript/ql/lib/semmle/javascript/dataflow/Nodes.qll

Lines changed: 48 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -898,17 +898,31 @@ class ClassNode extends DataFlow::SourceNode instanceof ClassNode::Range {
898898
*/
899899
FunctionNode getAnInstanceMember() { result = super.getAnInstanceMember(_) }
900900

901+
/**
902+
* Gets the static method, getter, or setter declared in this class with the given name and kind.
903+
*/
904+
FunctionNode getStaticMember(string name, MemberKind kind) {
905+
result = super.getStaticMember(name, kind)
906+
}
907+
901908
/**
902909
* Gets the static method declared in this class with the given name.
903910
*/
904-
FunctionNode getStaticMethod(string name) { result = super.getStaticMethod(name) }
911+
FunctionNode getStaticMethod(string name) {
912+
result = this.getStaticMember(name, MemberKind::method())
913+
}
914+
915+
/**
916+
* Gets a static method, getter, or setter declared in this class with the given kind.
917+
*/
918+
FunctionNode getAStaticMember(MemberKind kind) { result = super.getAStaticMember(kind) }
905919

906920
/**
907921
* Gets a static method declared in this class.
908922
*
909923
* The constructor is not considered a static method.
910924
*/
911-
FunctionNode getAStaticMethod() { result = super.getAStaticMethod() }
925+
FunctionNode getAStaticMethod() { result = this.getAStaticMember(MemberKind::method()) }
912926

913927
/**
914928
* Gets a dataflow node that refers to the superclass of this class.
@@ -1119,18 +1133,34 @@ module ClassNode {
11191133
abstract FunctionNode getAnInstanceMember(MemberKind kind);
11201134

11211135
/**
1136+
* Gets the static member of this class with the given name and kind.
1137+
*/
1138+
cached
1139+
abstract FunctionNode getStaticMember(string name, MemberKind kind);
1140+
1141+
/**
1142+
* DEPRECATED. Override `getStaticMember` instead.
1143+
*
11221144
* Gets the static method of this class with the given name.
11231145
*/
11241146
cached
1125-
abstract FunctionNode getStaticMethod(string name);
1147+
deprecated FunctionNode getStaticMethod(string name) { none() }
1148+
1149+
/**
1150+
* Gets a static member of this class of the given kind.
1151+
*/
1152+
cached
1153+
abstract FunctionNode getAStaticMember(MemberKind kind);
11261154

11271155
/**
1156+
* DEPRECATED. Override `getAStaticMember` instead.
1157+
*
11281158
* Gets a static method of this class.
11291159
*
11301160
* The constructor is not considered a static method.
11311161
*/
11321162
cached
1133-
abstract FunctionNode getAStaticMethod();
1163+
deprecated FunctionNode getAStaticMethod() { none() }
11341164

11351165
/**
11361166
* Gets a dataflow node representing a class to be used as the super-class
@@ -1186,23 +1216,27 @@ module ClassNode {
11861216
result = this.getConstructor().getReceiver().getAPropertySource()
11871217
}
11881218

1189-
override FunctionNode getStaticMethod(string name) {
1219+
override FunctionNode getStaticMember(string name, MemberKind kind) {
11901220
exists(MethodDeclaration method |
11911221
method = astNode.getMethod(name) and
11921222
method.isStatic() and
1223+
kind = MemberKind::of(method) and
11931224
result = method.getBody().flow()
11941225
)
11951226
or
1227+
kind.isMethod() and
11961228
result = this.getAPropertySource(name)
11971229
}
11981230

1199-
override FunctionNode getAStaticMethod() {
1231+
override FunctionNode getAStaticMember(MemberKind kind) {
12001232
exists(MethodDeclaration method |
12011233
method = astNode.getAMethod() and
12021234
method.isStatic() and
1235+
kind = MemberKind::of(method) and
12031236
result = method.getBody().flow()
12041237
)
12051238
or
1239+
kind.isMethod() and
12061240
result = this.getAPropertySource()
12071241
}
12081242

@@ -1300,9 +1334,15 @@ module ClassNode {
13001334
)
13011335
}
13021336

1303-
override FunctionNode getStaticMethod(string name) { result = this.getAPropertySource(name) }
1337+
override FunctionNode getStaticMember(string name, MemberKind kind) {
1338+
kind.isMethod() and
1339+
result = this.getAPropertySource(name)
1340+
}
13041341

1305-
override FunctionNode getAStaticMethod() { result = this.getAPropertySource() }
1342+
override FunctionNode getAStaticMember(MemberKind kind) {
1343+
kind.isMethod() and
1344+
result = this.getAPropertySource()
1345+
}
13061346

13071347
/**
13081348
* Gets a reference to the prototype of this class.

javascript/ql/lib/semmle/javascript/dataflow/internal/CallGraphs.qll

Lines changed: 31 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -189,25 +189,43 @@ module CallGraph {
189189
)
190190
}
191191

192+
/**
193+
* Holds if `ref` installs an accessor on an object. Such property writes should not
194+
* be considered calls to an accessor.
195+
*/
196+
pragma[nomagic]
197+
private predicate isAccessorInstallation(DataFlow::PropWrite write) {
198+
exists(write.getInstalledAccessor(_))
199+
}
200+
192201
/**
193202
* Gets a getter or setter invoked as a result of the given property access.
194203
*/
195204
cached
196205
DataFlow::FunctionNode getAnAccessorCallee(DataFlow::PropRef ref) {
197-
exists(DataFlow::ClassNode cls, string name |
198-
ref = cls.getAnInstanceMemberAccess(name) and
199-
result = cls.getInstanceMember(name, DataFlow::MemberKind::getter())
200-
or
201-
ref = getAnInstanceMemberAssignment(cls, name) and
202-
result = cls.getInstanceMember(name, DataFlow::MemberKind::setter())
203-
)
204-
or
205-
exists(DataFlow::ObjectLiteralNode object, string name |
206-
ref = getAnAllocationSiteRef(object).getAPropertyRead(name) and
207-
result = object.getPropertyGetter(name)
206+
not isAccessorInstallation(ref) and
207+
(
208+
exists(DataFlow::ClassNode cls, string name |
209+
ref = cls.getAnInstanceMemberAccess(name) and
210+
result = cls.getInstanceMember(name, DataFlow::MemberKind::getter())
211+
or
212+
ref = getAnInstanceMemberAssignment(cls, name) and
213+
result = cls.getInstanceMember(name, DataFlow::MemberKind::setter())
214+
or
215+
ref = cls.getAClassReference().getAPropertyRead(name) and
216+
result = cls.getStaticMember(name, DataFlow::MemberKind::getter())
217+
or
218+
ref = cls.getAClassReference().getAPropertyWrite(name) and
219+
result = cls.getStaticMember(name, DataFlow::MemberKind::setter())
220+
)
208221
or
209-
ref = getAnAllocationSiteRef(object).getAPropertyWrite(name) and
210-
result = object.getPropertySetter(name)
222+
exists(DataFlow::ObjectLiteralNode object, string name |
223+
ref = getAnAllocationSiteRef(object).getAPropertyRead(name) and
224+
result = object.getPropertyGetter(name)
225+
or
226+
ref = getAnAllocationSiteRef(object).getAPropertyWrite(name) and
227+
result = object.getPropertySetter(name)
228+
)
211229
)
212230
}
213231

javascript/ql/lib/semmle/javascript/dataflow/internal/FlowSteps.qll

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,7 @@ private module CachedSteps {
139139
* Holds if `invk` may invoke `f`.
140140
*/
141141
cached
142-
predicate calls(DataFlow::SourceNode invk, Function f) {
142+
predicate calls(DataFlow::Node invk, Function f) {
143143
f = invk.(DataFlow::InvokeNode).getACallee(0)
144144
or
145145
f = invk.(DataFlow::PropRef).getAnAccessorCallee().getFunction()
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
category: minorAnalysis
3+
---
4+
* The call graph now deals more precisely with calls to accessors (getters and setters).
5+
Previously, calls to static accessors were not resolved, and some method calls were
6+
incorrectly seen as calls to an accessor. Both issues have been fixed.
Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,15 @@
11
spuriousCallee
22
missingCallee
3-
| constructor-field.ts:40:5:40:14 | f3.build() | constructor-field.ts:13:3:13:12 | build() {} | -1 |
4-
| constructor-field.ts:71:1:71:11 | bf3.build() | constructor-field.ts:13:3:13:12 | build() {} | -1 |
3+
| constructor-field.ts:40:5:40:14 | f3.build() | constructor-field.ts:13:3:13:12 | build() {} | -1 | calls |
4+
| constructor-field.ts:71:1:71:11 | bf3.build() | constructor-field.ts:13:3:13:12 | build() {} | -1 | calls |
55
badAnnotation
6+
accessorCall
7+
| accessors.js:12:1:12:5 | obj.f | accessors.js:5:8:5:12 | () {} |
8+
| accessors.js:15:1:15:5 | obj.f | accessors.js:8:8:8:13 | (x) {} |
9+
| accessors.js:26:1:26:3 | C.f | accessors.js:19:15:19:19 | () {} |
10+
| accessors.js:29:1:29:3 | C.f | accessors.js:22:15:22:20 | (x) {} |
11+
| accessors.js:41:1:41:9 | new D().f | accessors.js:34:8:34:12 | () {} |
12+
| accessors.js:44:1:44:9 | new D().f | accessors.js:37:8:37:13 | (x) {} |
13+
| accessors.js:48:1:48:5 | obj.f | accessors.js:5:8:5:12 | () {} |
14+
| accessors.js:51:1:51:3 | C.f | accessors.js:19:15:19:19 | () {} |
15+
| accessors.js:54:1:54:9 | new D().f | accessors.js:34:8:34:12 | () {} |

javascript/ql/test/library-tests/CallGraphs/AnnotatedTest/Test.ql

Lines changed: 34 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -25,42 +25,62 @@ class AnnotatedFunction extends Function {
2525
}
2626

2727
/** A function annotated with `calls:NAME` */
28-
class AnnotatedCall extends InvokeExpr {
28+
class AnnotatedCall extends DataFlow::Node {
2929
string calls;
30+
string kind;
3031

31-
AnnotatedCall() { calls = getAnnotation(this, "calls") }
32+
AnnotatedCall() {
33+
this instanceof DataFlow::InvokeNode and
34+
calls = getAnnotation(this.asExpr(), kind) and
35+
kind = "calls"
36+
or
37+
this instanceof DataFlow::PropRef and
38+
calls = getAnnotation(this.getAstNode(), kind) and
39+
kind = "callsAccessor"
40+
}
3241

3342
string getCallTargetName() { result = calls }
3443

35-
AnnotatedFunction getAnExpectedCallee() { result.getCalleeName() = getCallTargetName() }
44+
AnnotatedFunction getAnExpectedCallee(string kind_) {
45+
result.getCalleeName() = getCallTargetName() and
46+
kind = kind_
47+
}
3648

37-
int getBoundArgs() { result = getAnnotation(this, "boundArgs").toInt() }
49+
int getBoundArgs() { result = getAnnotation(this.getAstNode(), "boundArgs").toInt() }
3850

3951
int getBoundArgsOrMinusOne() {
4052
result = getBoundArgs()
4153
or
4254
not exists(getBoundArgs()) and
4355
result = -1
4456
}
57+
58+
string getKind() { result = kind }
4559
}
4660

4761
predicate callEdge(AnnotatedCall call, AnnotatedFunction target, int boundArgs) {
48-
FlowSteps::calls(call.flow(), target) and boundArgs = -1
62+
FlowSteps::calls(call, target) and boundArgs = -1
4963
or
50-
FlowSteps::callsBound(call.flow(), target, boundArgs)
64+
FlowSteps::callsBound(call, target, boundArgs)
5165
}
5266

53-
query predicate spuriousCallee(AnnotatedCall call, AnnotatedFunction target, int boundArgs) {
67+
query predicate spuriousCallee(
68+
AnnotatedCall call, AnnotatedFunction target, int boundArgs, string kind
69+
) {
5470
callEdge(call, target, boundArgs) and
71+
kind = call.getKind() and
5572
not (
56-
target = call.getAnExpectedCallee() and
73+
target = call.getAnExpectedCallee(kind) and
5774
boundArgs = call.getBoundArgsOrMinusOne()
5875
)
5976
}
6077

61-
query predicate missingCallee(AnnotatedCall call, AnnotatedFunction target, int boundArgs) {
78+
query predicate missingCallee(
79+
AnnotatedCall call, AnnotatedFunction target, int boundArgs, string kind
80+
) {
6281
not callEdge(call, target, boundArgs) and
63-
target = call.getAnExpectedCallee() and
82+
kind = call.getKind() and
83+
target = call.getAnExpectedCallee(kind) and
6484
boundArgs = call.getBoundArgsOrMinusOne()
6585
}
6686

@@ -72,3 +92,7 @@ query predicate badAnnotation(string name) {
7292
not name = any(AnnotatedCall cl).getCallTargetName() and
7393
name = any(AnnotatedFunction cl).getCalleeName()
7494
}
95+
96+
query predicate accessorCall(DataFlow::PropRef ref, Function target) {
97+
FlowSteps::calls(ref, target)
98+
}

0 commit comments

Comments
 (0)