update the JS API-graph labels toString() to print the predicate calls on the API-graphs

This commit is contained in:
Erik Krogh Kristensen
2022-03-28 13:19:16 +02:00
parent 57c39e9642
commit c5fb19c377
29 changed files with 97 additions and 83 deletions

View File

@@ -1151,28 +1151,28 @@ module API {
/** Gets the EntryPoint associated with this label. */
API::EntryPoint getEntryPoint() { result = e }
override string toString() { result = e }
override string toString() { result = "getASuccessor(Label::entryPoint(\"" + e + "\"))" }
}
/** A label that gets a promised value. */
class LabelPromised extends ApiLabel {
LabelPromised() { this = MkLabelPromised() }
override string toString() { result = "promised" }
override string toString() { result = "getPromised()" }
}
/** A label that gets a rejected promise. */
class LabelPromisedError extends ApiLabel {
LabelPromisedError() { this = MkLabelPromisedError() }
override string toString() { result = "promisedError" }
override string toString() { result = "getPromisedError()" }
}
/** A label that gets the return value of a function. */
class LabelReturn extends ApiLabel {
LabelReturn() { this = MkLabelReturn() }
override string toString() { result = "return" }
override string toString() { result = "getReturn()" }
}
/** A label for a module. */
@@ -1184,14 +1184,15 @@ module API {
/** Gets the module associated with this label. */
string getMod() { result = mod }
override string toString() { result = "module " + mod }
// moduleImport is not neccesarilly the predicate to use, but it's close enough for most cases.
override string toString() { result = "moduleImport(\"" + mod + "\")" }
}
/** A label that gets an instance from a `new` call. */
class LabelInstance extends ApiLabel {
LabelInstance() { this = MkLabelInstance() }
override string toString() { result = "instance" }
override string toString() { result = "getInstance()" }
}
/** A label for the member named `prop`. */
@@ -1203,14 +1204,14 @@ module API {
/** Gets the property associated with this label. */
string getProperty() { result = prop }
override string toString() { result = "member " + prop }
override string toString() { result = "getMember(\"" + prop + "\")" }
}
/** A label for a member with an unknown name. */
class LabelUnknownMember extends ApiLabel {
LabelUnknownMember() { this = MkLabelUnknownMember() }
override string toString() { result = "member *" }
override string toString() { result = "getUnknownMember()" }
}
/** A label for parameter `i`. */
@@ -1219,7 +1220,7 @@ module API {
LabelParameter() { this = MkLabelParameter(i) }
override string toString() { result = "parameter " + i }
override string toString() { result = "getParameter(" + i + ")" }
/** Gets the index of the parameter for this label. */
int getIndex() { result = i }
@@ -1227,7 +1228,7 @@ module API {
/** A label for the receiver of call, that is, the value passed as `this`. */
class LabelReceiver extends ApiLabel, MkLabelReceiver {
override string toString() { result = "receiver" }
override string toString() { result = "getReceiver()" }
}
}
}

View File

@@ -1,12 +1,11 @@
/**
* A test query that verifies assertions about the API graph embedded in source-code comments.
*
* An assertion is a comment of the form `def <path>` or `use <path>`, and asserts that
* there is a def/use feature reachable from the root along the given path (described using
* s-expression syntax), and its associated data-flow node must start on the same line as the
* comment.
* An assertion is a comment of the form `def=<path>` or `use=<path>`, and asserts that
* there is a def/use feature reachable from the root along the given path, and its
* associated data-flow node must start on the same line as the comment.
*
* We also support negative assertions of the form `!def <path>` or `!use <path>`, which assert
* We also support negative assertions of the form `MISSING: def <path>` or `MISSING: use <path>`, which assert
* that there _isn't_ a node with the given path on the same line.
*
* The query only produces output for failed assertions, meaning that it should have no output
@@ -39,44 +38,55 @@ private string getLoc(DataFlow::Node nd) {
* An assertion matching a data-flow node against an API-graph feature.
*/
class Assertion extends Comment {
string polarity;
string expectedKind;
string expectedLoc;
string path;
string polarity;
Assertion() {
exists(string txt, string rex |
txt = this.getText().trim() and
rex = "(!?)(def|use) .*"
rex = ".*?((?:MISSING: )?)(def|use)=([\\w\\(\\)\"\\.\\-\\/\\@\\:]*).*"
|
polarity = txt.regexpCapture(rex, 1) and
expectedKind = txt.regexpCapture(rex, 2) and
path = txt.regexpCapture(rex, 3) and
expectedLoc = this.getFile().getAbsolutePath() + ":" + this.getLocation().getStartLine()
)
}
string getEdgeLabel(int i) { result = this.getText().regexpFind("(?<=\\()[^()]+", i, _).trim() }
string getEdgeLabel(int i) {
// matches a single edge. E.g. `getParameter(1)` or `getMember("foo")`.
// The lookbehind/lookahead ensure that the boundary is correct, that is
// either the edge is next to a ".", or it's the end of the string.
result = path.regexpFind("(?<=\\.|^)([\\w\\(\\)\"\\-\\/\\@\\:]+)(?=\\.|$)", i, _).trim()
}
int getPathLength() { result = max(int i | exists(this.getEdgeLabel(i))) + 1 }
predicate isNegative() { polarity = "MISSING: " }
API::Node lookup(int i) {
i = this.getPathLength() and
i = 0 and
result = API::root()
or
result =
this.lookup(i + 1)
.getASuccessor(any(API::Label::ApiLabel label | label.toString() = this.getEdgeLabel(i)))
this.lookup(i - 1)
.getASuccessor(any(API::Label::ApiLabel label |
label.toString() = this.getEdgeLabel(i - 1)
))
}
predicate isNegative() { polarity = "!" }
API::Node lookup() { result = this.lookup(this.getPathLength()) }
predicate holds() { getLoc(getNode(this.lookup(0), expectedKind)) = expectedLoc }
predicate holds() { getLoc(getNode(this.lookup(), expectedKind)) = expectedLoc }
string tryExplainFailure() {
exists(int i, API::Node nd, string prefix, string suffix |
nd = this.lookup(i) and
i > 0 and
not exists(this.lookup([0 .. i - 1])) and
prefix = nd + " has no outgoing edge labelled " + this.getEdgeLabel(i - 1) + ";" and
i < getPathLength() and
not exists(this.lookup([i + 1 .. getPathLength()])) and
prefix = nd + " has no outgoing edge labelled " + this.getEdgeLabel(i) + ";" and
if exists(nd.getASuccessor())
then
suffix =
@@ -91,13 +101,13 @@ class Assertion extends Comment {
result = prefix + " " + suffix
)
or
exists(API::Node nd, string kind | nd = this.lookup(0) |
exists(API::Node nd, string kind | nd = this.lookup() |
exists(getNode(nd, kind)) and
not exists(getNode(nd, expectedKind)) and
result = "Expected " + expectedKind + " node, but found " + kind + " node."
)
or
exists(DataFlow::Node nd | nd = getNode(this.lookup(0), expectedKind) |
exists(DataFlow::Node nd | nd = getNode(this.lookup(), expectedKind) |
not getLoc(nd) = expectedLoc and
result = "Node not found on this line (but there is one on line " + min(getLoc(nd)) + ")."
)

View File

@@ -1,6 +1,6 @@
const assert = require("assert");
let o = {
foo: 23 /* def (member foo (parameter 0 (member equal (member exports (module assert))))) */
foo: 23 // def=moduleImport("assert").getMember("exports").getMember("equal").getParameter(0).getMember("foo")
};
assert.equal(o, o);

View File

@@ -1,5 +1,5 @@
const fs = require('fs-extra');
module.exports.foo = async function foo() {
return await fs.copy('/tmp/myfile', '/tmp/mynewfile'); /* use (promised (return (member copy (member exports (module fs-extra))))) */ /* def (promised (return (member foo (member exports (module async-await))))) */
return await fs.copy('/tmp/myfile', '/tmp/mynewfile'); /* use=moduleImport("fs-extra").getMember("exports").getMember("copy").getReturn().getPromised()*/ /* def=moduleImport("async-await").getMember("exports").getMember("foo").getReturn().getPromised() */
};

View File

@@ -5,5 +5,5 @@ async function readFileUtf8(path: string): Promise<string> {
}
async function test(path: string) {
await readFileUtf8(path); /* use (promised (return (member readFile (member exports (module fs/promises))))) */
await readFileUtf8(path); /* use=moduleImport("fs/promises").getMember("exports").getMember("readFile").getReturn() */
}

View File

@@ -1,24 +1,24 @@
import bar from 'foo';
let boundbar = bar.bind(
"receiver", // def (receiver (member default (member exports (module foo))))
"firstarg" // def (parameter 0 (member default (member exports (module foo))))
"receiver", // def=moduleImport("foo").getMember("exports").getMember("default").getReceiver()
"firstarg" // def=moduleImport("foo").getMember("exports").getMember("default").getParameter(0)
);
boundbar(
"secondarg" // def (parameter 1 (member default (member exports (module foo))))
"secondarg" // def=moduleImport("foo").getMember("exports").getMember("default").getParameter(1)
)
let boundbar2 = boundbar.bind(
"ignored", // !def (receiver (member default (member exports (module foo))))
"othersecondarg" // def (parameter 1 (member default (member exports (module foo))))
"ignored", // MISSING: def=moduleImport("foo").getMember("exports)".getMember("default").getReceiver()
"othersecondarg" // def=moduleImport("foo").getMember("exports").getMember("default").getParameter(1)
)
boundbar2(
"thirdarg" // def (parameter 2 (member default (member exports (module foo))))
"thirdarg" // def=moduleImport("foo").getMember("exports").getMember("default").getParameter(2)
)
let bar2 = bar;
for (var i = 0; i < 2; ++i)
bar2 = bar2.bind(
null,
i /* def (parameter 1 (member default (member exports (module foo)))) */ /* def (parameter 9 (member default (member exports (module foo)))) */
i /* def=moduleImport("foo").getMember("exports").getMember("default").getParameter(1) */ /* def=moduleImport("foo").getMember("exports").getMember("default").getParameter(9) */
);

View File

@@ -3,5 +3,5 @@ const fs = require('fs');
exports.foo = function (cb) {
if (!cb)
cb = function () { };
cb(fs.readFileSync("/etc/passwd")); /* def (parameter 0 (parameter 0 (member foo (member exports (module branching-flow))))) */
cb(fs.readFileSync("/etc/passwd")); /* def=moduleImport("branching-flow").getMember("exports").getMember("foo").getParameter(0).getParameter(0) */
};

View File

@@ -9,19 +9,19 @@ util.inherits(MyStream, EventEmitter);
MyStream.prototype.write = (data) => this.emit('data', data);
function MyOtherStream() { /* use (instance (member MyOtherStream (member exports (module classes)))) */
function MyOtherStream() { /* use=moduleImport("classes").getMember("exports").getMember("MyOtherStream").getInstance() */
EventEmitter.call(this);
}
util.inherits(MyOtherStream, EventEmitter);
MyOtherStream.prototype.write = function (data) { /* use (instance (member MyOtherStream (member exports (module classes)))) */
MyOtherStream.prototype.write = function (data) { /* use=moduleImport("classes").getMember("exports").getMember("MyOtherStream").getInstance() */
this.emit('data', data);
return this;
};
MyOtherStream.prototype.instanceProp = 1; /* def (member instanceProp (instance (member MyOtherStream (member exports (module classes))))) */
MyOtherStream.prototype.instanceProp = 1; /* def=moduleImport("classes").getMember("exports").getMember("MyOtherStream").getInstance().getMember("instanceProp") */
MyOtherStream.classProp = 1; /* def (member classProp (member MyOtherStream (member exports (module classes)))) */
MyOtherStream.classProp = 1; /* def=moduleImport("classes").getMember("exports").getMember("MyOtherStream").getMember("classProp") */
module.exports.MyOtherStream = MyOtherStream;

View File

@@ -1,5 +1,5 @@
export class A {
constructor(x) { /* use (parameter 0 (member A (member exports (module ctor-arg)))) */
constructor(x) { /* use=moduleImport("ctor-arg").getMember("exports").getMember("A").getParameter(0) */
console.log(x);
}
}

View File

@@ -1 +1 @@
module.exports = CustomEntryPoint.foo; /* use (member foo (CustomEntryPoint)) */
module.exports = CustomEntryPoint.foo; /* use=getASuccessor(Label::entryPoint("CustomEntryPoint")) */

View File

@@ -1,4 +1,4 @@
const foo = require("foo");
while(foo)
foo = foo.foo; /* use (member foo (member exports (module foo))) */ /* use (member foo (member foo (member exports (module foo)))) */
foo = foo.foo; /* use=moduleImport("foo").getMember("exports").getMember("foo") */ /* use=moduleImport("foo").getMember("exports").getMember("foo").getMember("foo") */

View File

@@ -2,4 +2,4 @@ const MyStream = require('classes').MyStream;
var s = new MyStream();
for (let m of ["write"])
s[m]("Hello, world!"); /* use (member * (instance (member MyStream (member exports (module classes))))) */
s[m]("Hello, world!"); /* use=moduleImport("classes").getMember("exports").getMember("MyStream").getInstance().getUnknownMember() */

View File

@@ -1,3 +1,3 @@
anotherUnknownFunction().foo = 42; /* !def (member foo (member exports (module imprecise-export))) */
anotherUnknownFunction().foo = 42; /* MISSING: def=moduleExport("imprecise-export").getMember("exports").getMember("foo") */
module.exports = unknownFunction();

View File

@@ -1,11 +1,11 @@
const http = require('http');
let req = http.get(url, cb);
req.on('connect', (
req, /* use (parameter 0 (parameter 1 (member on (return (member get (member exports (module http))))))) */
req, /* use=moduleImport("http").getMember("exports").getMember("get").getReturn().getMember("on").getParameter(1).getParameter(0) */
clientSocket, head) => { /* ... */ });
req.on('information', (
info /* use (parameter 0 (parameter 1 (member on (return (member get (member exports (module http))))))) */
info /* use=moduleImport("http").getMember("exports").getMember("get").getReturn().getMember("on").getParameter(1).getParameter(0) */
) => { /* ... */ });
req.on('connect', () => { }) /* def (parameter 0 (member on (return (member get (member exports (module http)))))) */
.on('information', () => { }) /* def (parameter 0 (member on (return (member on (return (member get (member exports (module http)))))))) */;
req.on('connect', () => { }) /* def=moduleImport("http").getMember("exports").getMember("get").getReturn().getMember("on").getParameter(0) */
.on('information', () => { }) /* def=moduleImport("http").getMember("exports").getMember("get").getReturn().getMember("on").getReturn().getMember("on").getParameter(0) */;

View File

@@ -1,2 +1,2 @@
import foo from "@myorg/myotherpkg";
foo(); /* use (member default (member exports (module @myorg/myotherpkg))) */
foo(); /* use=moduleImport("@myorg/myotherpkg").getMember("exports").getMember("default") */

View File

@@ -1,7 +1,7 @@
module.exports.foo = function (x) { /* use (parameter 0 (member foo (member exports (module nested-property-export)))) */
module.exports.foo = function (x) { /* use=moduleImport("nested-property-export").getMember("exports").getMember("foo").getParameter(0) */
return x;
};
module.exports.foo.bar = function (y) { /* use (parameter 0 (member bar (member foo (member exports (module nested-property-export))))) */
module.exports.foo.bar = function (y) { /* use=moduleImport("nested-property-export").getMember("exports").getMember("foo").getMember("bar").getParameter(0) */
return y;
};

View File

@@ -2,7 +2,7 @@ const express = require('express');
var app1 = new express();
app1.get('/',
(req, res) => res.send('Hello World!') /* def (parameter 1 (member get (instance (member exports (module express))))) */
(req, res) => res.send('Hello World!') /* def=moduleImport("express").getMember("exports").getInstance().getMember("get").getParameter(1) */
);
function makeApp() {
@@ -11,5 +11,5 @@ function makeApp() {
var app2 = makeApp();
app2.get('/',
(req, res) => res.send('Hello World!') /* def (parameter 1 (member get (instance (member exports (module express))))) */
(req, res) => res.send('Hello World!') /* def=moduleImport("express").getMember("exports").getInstance().getMember("get").getParameter(1) */
);

View File

@@ -2,7 +2,7 @@ const cp = require('child_process');
module.exports = function () {
return cp.spawn.bind(
cp, // def (receiver (member spawn (member exports (module child_process))))
"cat" // def (parameter 0 (member spawn (member exports (module child_process))))
cp, // def=moduleImport("child_process").getMember("exports").getMember("spawn").getReceiver()
"cat" // def=moduleImport("child_process").getMember("exports").getMember("spawn").getParameter(0)
);
};

View File

@@ -8,14 +8,14 @@ module.exports.readFile = function (f) {
if (err)
rej(err);
else
res(data); /* def (promised (return (member readFile (member exports (module promises))))) */
res(data); /* def=moduleImport("promises").getMember("exports").getMember("readFile").getReturn().getPromised() */
});
});
};
module.exports.readFileAndEncode = function (f) {
return fse.readFile(f)
.then((data) => /* use (promised (return (member readFile (member exports (module fs-extra))))) */
base64.encode(data) /* def (promised (return (member readFileAndEncode (member exports (module promises))))) */
.then((data) => /* use=moduleImport("fs-extra").getMember("exports").getMember("readFile").getReturn().getPromised() */
base64.encode(data) /* def=moduleImport("promises").getMember("exports").getMember("readFileAndEncode").getReturn().getPromised() */
);
};

View File

@@ -4,25 +4,25 @@ var readFile = require("fs").readFile;
var readFileAsync = bluebird.promisify(readFile);
readFile(
"tst.txt", // def (parameter 0 (member readFile (member exports (module fs))))
"utf8", // def (parameter 1 (member readFile (member exports (module fs))))
"tst.txt", // def=moduleImport("fs").getMember("exports").getMember("readFile").getParameter(0)
"utf8", // def=moduleImport("fs").getMember("exports").getMember("readFile").getParameter(1)
function (
err, // use (parameter 0 (parameter 2 (member readFile (member exports (module fs)))))
contents // use (parameter 1 (parameter 2 (member readFile (member exports (module fs)))))
err, // use=moduleImport("fs").getMember("exports").getMember("readFile").getParameter(2).getParameter(0)
contents // use=moduleImport("fs").getMember("exports").getMember("readFile").getParameter(2).getParameter(1)
) { });
readFileAsync(
"tst.txt" // def (parameter 0 (member readFile (member exports (module fs))))
"tst.txt" // def=moduleImport("fs").getMember("exports").getMember("readFile").getParameter(0)
).then(
function (buf) { } // use (parameter 1 (parameter 1 (member readFile (member exports (module fs)))))
function (buf) { } // use=moduleImport("fs").getMember("exports").getMember("readFile").getParameter(1).getParameter(1)
).catch(
function (err) { } // not yet modelled: (parameter 0 (parameter 1 (member readFile (member exports (module fs)))))
);
try {
let p = readFileAsync(
"tst.txt", // def (parameter 0 (member readFile (member exports (module fs))))
"utf8" // def (parameter 1 (member readFile (member exports (module fs))))
"tst.txt", // def=moduleImport("fs").getMember("exports").getMember("readFile").getParameter(0)
"utf8" // def=moduleImport("fs").getMember("exports").getMember("readFile").getParameter(1)
);
let data = await p; // use (parameter 1 (parameter 2 (member readFile (member exports (module fs)))))
let data = await p; // use=moduleImport("fs").getMember("exports").getMember("readFile").getParameter(2).getParameter(1)
} catch (e) { } // not yet modelled: (parameter 0 (parameter 2 (member readFile (member exports (module fs)))))

View File

@@ -5,5 +5,5 @@ exports.assertNotNull = function (x) {
exports.foo = function(x) {
exports.assertNotNull(x);
sink(x.f); /* !use (member f (parameter 0 (member assertNotNull (member exports (module property-read-from-argument))))) */ /* use (member f (parameter 0 (member foo (member exports (module property-read-from-argument))))) */
sink(x.f); /* MISSING: use=moduleImport("property-read-from-argument").getMember("exports").getMember("assertNotNull").getParameter(0).getMember("f") */ /* use=moduleImport("property-read-from-argument").getMember("exports").getMember("foo").getParameter(0).getMember("f") */
}

View File

@@ -1,3 +1,3 @@
module.exports = function () {
return 42; /* def (return (member impl (member exports (module reexport)))) */
return 42; /* def=moduleImport("reexport").getMember("exports").getMember("impl").getReturn() */
};

View File

@@ -1,4 +1,4 @@
function foo(x) { /* use (parameter 0 (member bar (member other (member exports (module reexport)))) */
function foo(x) { /* use=moduleImport("reexport").getMember("exports").getMember("other").getMember("bar").getParameter(0) */
return x + 1;
}

View File

@@ -1,3 +1,3 @@
module.exports.id = function id(x) { /* use (parameter 0 (member id (member util (member exports (module reexport)))) */
module.exports.id = function id(x) { /* use=moduleImport("reexport").getMember("exports").getMember("util").getMember("id").getParameter(0) */
return x;
};

View File

@@ -1,5 +1,5 @@
module.exports = {
id: function id(x) { /* use (parameter 0 (member id (member util2 (member exports (module reexport)))) */
id: function id(x) { /* use=moduleImport("reexport").getMember("exports").getMember("util2").getMember("id").getParameter(0) */
return x;
}
};

View File

@@ -1,6 +1,6 @@
export class A {
foo() {
return this; /* def (return (member foo (instance (member A (member exports (module return-self)))))) */
return this; /* def=moduleImport("return-self").getMember("exports").getMember("A").getInstance().getMember("foo").getReturn() */
}
bar(x) { } /* use (parameter 0 (member bar (instance (member A (member exports (module return-self)))))) */
bar(x) { } /* use=moduleImport("return-self").getMember("exports").getMember("A").getInstance().getMember("bar").getParameter(0) */
}

View File

@@ -2,7 +2,7 @@ const lib = require('something');
function f() {
return {
x: new Object() /* def (member x (parameter 0 (member m1 (member exports (module something))))) */
x: new Object() /* def=moduleImport("something").getMember("exports").getMember("m1").getParameter(0).getMember("x") */
}
}

View File

@@ -11,7 +11,7 @@ app.use(bodyParser.json());
app.post("/find", (req, res) => {
let v = JSON.parse(req.body.x);
getCollection().find({ id: v }); /* use (member find (instance (member Collection (member exports (module mongodb))))) */
getCollection().find({ id: v }); // use=moduleImport("mongodb").getMember("exports").getMember("Collection").getInstance().getMember("find")
});
import * as mongoose from "mongoose";
@@ -19,14 +19,14 @@ declare function getMongooseModel(): mongoose.Model;
declare function getMongooseQuery(): mongoose.Query;
app.post("/find", (req, res) => {
let v = JSON.parse(req.body.x);
getMongooseModel().find({ id: v }); /* def (parameter 0 (member find (instance (member Model (member exports (module mongoose)))))) */
getMongooseQuery().find({ id: v }); /* def (parameter 0 (member find (instance (member Query (member exports (module mongoose)))))) */
getMongooseModel().find({ id: v }); // def=moduleImport("mongoose").getMember("exports").getMember("Model").getInstance().getMember("find").getParameter(0)
getMongooseQuery().find({ id: v }); // def=moduleImport("mongoose").getMember("exports").getMember("Query").getInstance().getMember("find").getParameter(0)
});
import * as puppeteer from 'puppeteer';
class Renderer {
private browser: puppeteer.Browser;
foo(): void {
const page = this.browser.newPage(); /* use (instance (member Browser (member exports (module puppeteer)))) */
const page = this.browser.newPage(); /* use=moduleImport("puppeteer").getMember("exports").getMember("Browser").getInstance() */
}
}