JS: Add annotated call graph test case

This commit is contained in:
Asger F
2019-07-08 13:38:58 +01:00
parent b63f14fe94
commit 6019e48917
5 changed files with 206 additions and 0 deletions

View File

@@ -0,0 +1,5 @@
spuriousCallee
missingCallee
| constructor-field.ts:40:5:40:14 | f3.build() | constructor-field.ts:13:3:13:12 | build() {} |
| constructor-field.ts:71:1:71:11 | bf3.build() | constructor-field.ts:13:3:13:12 | build() {} |
badAnnotation

View File

@@ -0,0 +1,60 @@
import javascript
import semmle.javascript.dataflow.internal.FlowSteps as FlowSteps
/**
* Gets the value of a tag of form `tag:value` in the JSDoc comment for `doc`.
*
* We avoid using JSDoc tags as the call graph construction may depend on them
* in the future.
*/
string getAnnotation(Documentable doc, string tag) {
exists(string text |
text = doc.getDocumentation().getComment().getText().regexpFind("[\\w]+:[\\w\\d.]+", _, _) and
tag = text.regexpCapture("([\\w]+):.*", 1) and
result = text.regexpCapture(".*:([\\w\\d.]+)", 1)
)
}
/** A function annotated with `name:NAME` */
class AnnotatedFunction extends Function {
string name;
AnnotatedFunction() { name = getAnnotation(this, "name") }
string getCalleeName() {
result = name
}
}
/** A function annotated with `calls:NAME` */
class AnnotatedCall extends InvokeExpr {
string calls;
AnnotatedCall() { calls = getAnnotation(this, "calls") }
string getCallTargetName() {
result = calls
}
AnnotatedFunction getAnExpectedCallee() {
result.getCalleeName() = getCallTargetName()
}
}
query predicate spuriousCallee(AnnotatedCall call, AnnotatedFunction target) {
FlowSteps::calls(call.flow(), target) and
not target = call.getAnExpectedCallee()
}
query predicate missingCallee(AnnotatedCall call, AnnotatedFunction target) {
not FlowSteps::calls(call.flow(), target) and
target = call.getAnExpectedCallee()
}
query predicate badAnnotation(string name) {
name = any(AnnotatedCall cl).getCallTargetName() and
not name = any(AnnotatedFunction cl).getCalleeName()
or
not name = any(AnnotatedCall cl).getCallTargetName() and
name = any(AnnotatedFunction cl).getCalleeName()
}

View File

@@ -0,0 +1,71 @@
class Factory1 {
/** name:Factory1.build */
build() {}
}
class Factory2 {
/** name:Factory2.build */
build() {}
}
class Factory3 {
/** name:Factory3.build */
build() {}
}
class Builder {
factory1 = new Factory1();
constructor(x) {
this.factory2 = new Factory2();
this.factory3 = x;
}
method() {
/** calls:Factory1.build */
this.factory1.build();
/** calls:Factory2.build */
this.factory2.build();
/** calls:Factory3.build */
this.factory3.build();
let f1 = this.getFactory1();
let f2 = this.getFactory2();
let f3 = this.getFactory3();
/** calls:Factory1.build */
f1.build();
/** calls:Factory2.build */
f2.build();
/** calls:Factory3.build */
f3.build();
/** calls:Builder.build */
this.build();
}
getFactory1() {
return this.factory1;
}
getFactory2() {
return this.factory2;
}
getFactory3() {
return this.factory3;
}
/** name:Builder.build */
build() {}
}
let b = new Builder(new Factory3());
let bf1 = b.getFactory1();
let bf2 = b.getFactory2();
let bf3 = b.getFactory3();
/** calls:Factory1.build */
bf1.build();
/** calls:Factory2.build */
bf2.build();
/** calls:Factory3.build */
bf3.build();

View File

@@ -0,0 +1,35 @@
import 'dummy';
class A {
/** name:A.f */
f() {}
}
class B {
/** name:B.f */
f() {}
}
function g(a) {
/** calls:A.f */
a.f();
}
g(new A);
g(new A);
function h(b) {
/** calls:B.f */
b.f();
}
h(new B);
h(new B);
function either(ab) {
/**
* calls:A.f
* calls:B.f
*/
ab.f();
}
either(new A);
either(new B);

View File

@@ -0,0 +1,35 @@
import 'dummy';
class C {
/** name:C.f */
static f() {}
}
class D {
/** name:D.f */
static f() {}
}
function g(c) {
/** calls:C.f */
c.f();
}
g(C);
g(C);
function h(d) {
/** calls:D.f */
d.f();
}
h(D);
h(D);
function either(cd) {
/**
* calls:C.f
* calls:D.f
*/
cd.f();
}
either(C);
either(D);