mirror of
https://github.com/github/codeql.git
synced 2025-12-17 01:03:14 +01:00
JS: Add annotated call graph test case
This commit is contained in:
@@ -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
|
||||
@@ -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()
|
||||
}
|
||||
@@ -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();
|
||||
@@ -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);
|
||||
@@ -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);
|
||||
Reference in New Issue
Block a user