Skip to content

Commit c5d0aba

Browse files
authored
Merge pull request #1560 from asger-semmle/static-calls
Approved by xiemaisi
2 parents d8395b7 + 6019e48 commit c5d0aba

32 files changed

+209
-0
lines changed

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,9 @@ private module CachedSteps {
122122
exists(DataFlow::ClassNode cls, string name |
123123
callResolvesToMember(invk, cls, name) and
124124
f = cls.getInstanceMethod(name).getFunction()
125+
or
126+
invk = cls.getAClassReference().getAMethodCall(name) and
127+
f = cls.getStaticMethod(name).getFunction()
125128
)
126129
}
127130

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
spuriousCallee
2+
missingCallee
3+
| constructor-field.ts:40:5:40:14 | f3.build() | constructor-field.ts:13:3:13:12 | build() {} |
4+
| constructor-field.ts:71:1:71:11 | bf3.build() | constructor-field.ts:13:3:13:12 | build() {} |
5+
badAnnotation
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import javascript
2+
import semmle.javascript.dataflow.internal.FlowSteps as FlowSteps
3+
4+
/**
5+
* Gets the value of a tag of form `tag:value` in the JSDoc comment for `doc`.
6+
*
7+
* We avoid using JSDoc tags as the call graph construction may depend on them
8+
* in the future.
9+
*/
10+
string getAnnotation(Documentable doc, string tag) {
11+
exists(string text |
12+
text = doc.getDocumentation().getComment().getText().regexpFind("[\\w]+:[\\w\\d.]+", _, _) and
13+
tag = text.regexpCapture("([\\w]+):.*", 1) and
14+
result = text.regexpCapture(".*:([\\w\\d.]+)", 1)
15+
)
16+
}
17+
18+
/** A function annotated with `name:NAME` */
19+
class AnnotatedFunction extends Function {
20+
string name;
21+
22+
AnnotatedFunction() { name = getAnnotation(this, "name") }
23+
24+
string getCalleeName() {
25+
result = name
26+
}
27+
}
28+
29+
/** A function annotated with `calls:NAME` */
30+
class AnnotatedCall extends InvokeExpr {
31+
string calls;
32+
33+
AnnotatedCall() { calls = getAnnotation(this, "calls") }
34+
35+
string getCallTargetName() {
36+
result = calls
37+
}
38+
39+
AnnotatedFunction getAnExpectedCallee() {
40+
result.getCalleeName() = getCallTargetName()
41+
}
42+
}
43+
44+
query predicate spuriousCallee(AnnotatedCall call, AnnotatedFunction target) {
45+
FlowSteps::calls(call.flow(), target) and
46+
not target = call.getAnExpectedCallee()
47+
}
48+
49+
query predicate missingCallee(AnnotatedCall call, AnnotatedFunction target) {
50+
not FlowSteps::calls(call.flow(), target) and
51+
target = call.getAnExpectedCallee()
52+
}
53+
54+
query predicate badAnnotation(string name) {
55+
name = any(AnnotatedCall cl).getCallTargetName() and
56+
not name = any(AnnotatedFunction cl).getCalleeName()
57+
or
58+
not name = any(AnnotatedCall cl).getCallTargetName() and
59+
name = any(AnnotatedFunction cl).getCalleeName()
60+
}
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
class Factory1 {
2+
/** name:Factory1.build */
3+
build() {}
4+
}
5+
6+
class Factory2 {
7+
/** name:Factory2.build */
8+
build() {}
9+
}
10+
11+
class Factory3 {
12+
/** name:Factory3.build */
13+
build() {}
14+
}
15+
16+
class Builder {
17+
factory1 = new Factory1();
18+
19+
constructor(x) {
20+
this.factory2 = new Factory2();
21+
this.factory3 = x;
22+
}
23+
24+
method() {
25+
/** calls:Factory1.build */
26+
this.factory1.build();
27+
/** calls:Factory2.build */
28+
this.factory2.build();
29+
/** calls:Factory3.build */
30+
this.factory3.build();
31+
32+
let f1 = this.getFactory1();
33+
let f2 = this.getFactory2();
34+
let f3 = this.getFactory3();
35+
/** calls:Factory1.build */
36+
f1.build();
37+
/** calls:Factory2.build */
38+
f2.build();
39+
/** calls:Factory3.build */
40+
f3.build();
41+
42+
/** calls:Builder.build */
43+
this.build();
44+
}
45+
46+
getFactory1() {
47+
return this.factory1;
48+
}
49+
getFactory2() {
50+
return this.factory2;
51+
}
52+
getFactory3() {
53+
return this.factory3;
54+
}
55+
56+
/** name:Builder.build */
57+
build() {}
58+
}
59+
60+
let b = new Builder(new Factory3());
61+
62+
let bf1 = b.getFactory1();
63+
let bf2 = b.getFactory2();
64+
let bf3 = b.getFactory3();
65+
66+
/** calls:Factory1.build */
67+
bf1.build();
68+
/** calls:Factory2.build */
69+
bf2.build();
70+
/** calls:Factory3.build */
71+
bf3.build();
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import 'dummy';
2+
3+
class A {
4+
/** name:A.f */
5+
f() {}
6+
}
7+
8+
class B {
9+
/** name:B.f */
10+
f() {}
11+
}
12+
13+
function g(a) {
14+
/** calls:A.f */
15+
a.f();
16+
}
17+
g(new A);
18+
g(new A);
19+
20+
function h(b) {
21+
/** calls:B.f */
22+
b.f();
23+
}
24+
h(new B);
25+
h(new B);
26+
27+
function either(ab) {
28+
/**
29+
* calls:A.f
30+
* calls:B.f
31+
*/
32+
ab.f();
33+
}
34+
either(new A);
35+
either(new B);
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import 'dummy';
2+
3+
class C {
4+
/** name:C.f */
5+
static f() {}
6+
}
7+
8+
class D {
9+
/** name:D.f */
10+
static f() {}
11+
}
12+
13+
function g(c) {
14+
/** calls:C.f */
15+
c.f();
16+
}
17+
g(C);
18+
g(C);
19+
20+
function h(d) {
21+
/** calls:D.f */
22+
d.f();
23+
}
24+
h(D);
25+
h(D);
26+
27+
function either(cd) {
28+
/**
29+
* calls:C.f
30+
* calls:D.f
31+
*/
32+
cd.f();
33+
}
34+
either(C);
35+
either(D);
File renamed without changes.
File renamed without changes.
File renamed without changes.

javascript/ql/test/library-tests/CallGraphs/classes.js renamed to javascript/ql/test/library-tests/CallGraphs/FullTest/classes.js

File renamed without changes.

0 commit comments

Comments
 (0)