Merge pull request #4082 from max-schaefer/js/api-graph

Approved by asgerf
This commit is contained in:
CodeQL CI
2020-09-11 04:41:38 -07:00
committed by GitHub
85 changed files with 1354 additions and 253 deletions

View File

@@ -5,6 +5,7 @@
import Customizations
import semmle.javascript.Aliases
import semmle.javascript.AMD
import semmle.javascript.ApiGraphs
import semmle.javascript.Arrays
import semmle.javascript.AST
import semmle.javascript.BasicBlocks

View File

@@ -0,0 +1,746 @@
/**
* Provides an implementation of _API graphs_, which are an abstract representation of the API
* surface used and/or defined by a code base.
*
* The nodes of the API graph represent definitions and uses of API components. The edges are
* directed and labeled; they specify how the components represented by nodes relate to each other.
* For example, if one of the nodes represents a definition of an API function, then there
* will be nodes corresponding to the function's parameters, which are connected to the function
* node by edges labeled `parameter <i>`.
*/
import javascript
/**
* Provides classes and predicates for working with APIs defined or used in a database.
*/
module API {
/**
* An abstract representation of a definition or use of an API component such as a function
* exported by an npm package, a parameter of such a function, or its result.
*/
class Node extends Impl::TApiNode {
/**
* Gets a data-flow node corresponding to a use of the API component represented by this node.
*
* For example, `require('fs').readFileSync` is a use of the function `readFileSync` from the
* `fs` module, and `require('fs').readFileSync(file)` is a use of the result of that function.
*
* As another example, in the assignment `exports.plusOne = (x) => x+1` the two references to
* `x` are uses of the first parameter of `plusOne`.
*/
DataFlow::Node getAUse() {
exists(DataFlow::SourceNode src | Impl::use(this, src) |
Impl::trackUseNode(src).flowsTo(result)
)
}
/**
* Gets a data-flow node corresponding to the right-hand side of a definition of the API
* component represented by this node.
*
* For example, in the assignment `exports.plusOne = (x) => x+1`, the function expression
* `(x) => x+1` is the right-hand side of the definition of the member `plusOne` of
* the enclosing module, and the expression `x+1` is the right-had side of the definition of
* its result.
*
* Note that for parameters, it is the arguments flowing into that parameter that count as
* right-hand sides of the definition, not the declaration of the parameter itself.
* Consequently, in `require('fs').readFileSync(file)`, `file` is the right-hand
* side of a definition of the first parameter of `readFileSync` from the `fs` module.
*/
DataFlow::Node getARhs() { Impl::rhs(this, result) }
/**
* Gets a node representing member `m` of this API component.
*
* For example, modules have an `exports` member representing their exports, and objects have
* their properties as members.
*/
bindingset[m]
bindingset[result]
Node getMember(string m) { result = getASuccessor(Label::member(m)) }
/**
* Gets a node representing a member of this API component where the name of the member is
* not known statically.
*/
Node getUnknownMember() { result = getASuccessor(Label::unknownMember()) }
/**
* Gets a node representing a member of this API component where the name of the member may
* or may not be known statically.
*/
Node getAMember() {
result = getASuccessor(Label::member(_)) or
result = getUnknownMember()
}
/**
* Gets a node representing an instance of this API component, that is, an object whose
* constructor is the function represented by this node.
*
* For example, if this node represents a use of some class `A`, then there might be a node
* representing instances of `A`, typically corresponding to expressions `new A()` at the
* source level.
*/
Node getInstance() { result = getASuccessor(Label::instance()) }
/**
* Gets a node representing the `i`th parameter of the function represented by this node.
*/
bindingset[i]
Node getParameter(int i) { result = getASuccessor(Label::parameter(i)) }
/**
* Gets the number of parameters of the function represented by this node.
*/
int getNumParameter() {
result =
max(string s | exists(getASuccessor(Label::parameterByStringIndex(s))) | s.toInt()) + 1
}
/**
* Gets a node representing the last parameter of the function represented by this node.
*/
Node getLastParameter() { result = getParameter(getNumParameter() - 1) }
/**
* Gets a node representing the receiver of the function represented by this node.
*/
Node getReceiver() { result = getASuccessor(Label::receiver()) }
/**
* Gets a node representing a parameter or the receiver of the function represented by this
* node.
*/
Node getAParameter() {
result = getASuccessor(Label::parameterByStringIndex(_)) or
result = getReceiver()
}
/**
* Gets a node representing the result of the function represented by this node.
*/
Node getReturn() { result = getASuccessor(Label::return()) }
/**
* Gets a node representing the promised value wrapped in the `Promise` object represented by
* this node.
*/
Node getPromised() { result = getASuccessor(Label::promised()) }
/**
* Gets a string representation of the lexicographically least among all shortest access paths
* from the root to this node.
*/
string getPath() { result = min(string p | p = getAPath(Impl::distanceFromRoot(this)) | p) }
/**
* Gets a node such that there is an edge in the API graph between this node and the other
* one, and that edge is labeled with `lbl`.
*/
Node getASuccessor(string lbl) { Impl::edge(this, lbl, result) }
/**
* Gets a node such that there is an edge in the API graph between that other node and
* this one, and that edge is labeled with `lbl`
*/
Node getAPredecessor(string lbl) { this = result.getASuccessor(lbl) }
/**
* Gets a node such that there is an edge in the API graph between this node and the other
* one.
*/
Node getAPredecessor() { result = getAPredecessor(_) }
/**
* Gets a node such that there is an edge in the API graph between that other node and
* this one.
*/
Node getASuccessor() { result = getASuccessor(_) }
/**
* Holds if this node may take its value from `that` node.
*
* In other words, the value of a use of `that` may flow into the right-hand side of a
* definition of this node.
*/
predicate refersTo(Node that) { this.getARhs() = that.getAUse() }
/**
* Gets the data-flow node that gives rise to this node, if any.
*/
DataFlow::Node getInducingNode() {
this = Impl::MkClassInstance(result) or
this = Impl::MkUse(result) or
this = Impl::MkDef(result) or
this = Impl::MkAsyncFuncResult(result)
}
/**
* Holds if this node is located in file `path` between line `startline`, column `startcol`,
* and line `endline`, column `endcol`.
*
* For nodes that do not have a meaningful location, `path` is the empty string and all other
* parameters are zero.
*/
predicate hasLocationInfo(string path, int startline, int startcol, int endline, int endcol) {
getInducingNode().hasLocationInfo(path, startline, startcol, endline, endcol)
or
not exists(getInducingNode()) and
path = "" and
startline = 0 and
startcol = 0 and
endline = 0 and
endcol = 0
}
/**
* Gets a textual representation of this node.
*/
string toString() {
none() // defined in subclasses
}
/**
* Gets a path of the given `length` from the root to this node.
*/
private string getAPath(int length) {
this instanceof Impl::MkRoot and
length = 0 and
result = ""
or
exists(Node pred, string lbl, string predpath |
Impl::edge(pred, lbl, this) and
lbl != "" and
predpath = pred.getAPath(length - 1) and
exists(string space | if length = 1 then space = "" else space = " " |
result = "(" + lbl + space + predpath + ")" and
// avoid producing strings longer than 1MB
result.length() < 1000 * 1000
)
) and
length in [1 .. Impl::distanceFromRoot(this)]
}
}
/** The root node of an API graph. */
class Root extends Node, Impl::MkRoot {
override string toString() { result = "root" }
}
/** A node corresponding to a definition of an API component. */
class Definition extends Node, Impl::TDef {
override string toString() { result = "def " + getPath() }
}
/** A node corresponding to the use of an API component. */
class Use extends Node, Impl::TUse {
override string toString() { result = "use " + getPath() }
}
/** Gets the root node. */
Root root() { any() }
/** Gets a node corresponding to an import of module `m`. */
Node moduleImport(string m) {
result = Impl::MkModuleImport(m) or
result = Impl::MkModuleImport(m).(Node).getMember("default")
}
/** Gets a node corresponding to an export of module `m`. */
Node moduleExport(string m) { result = Impl::MkModuleDef(m).(Node).getMember("exports") }
/**
* An API entry point.
*
* Extend this class to define additional API entry points other than modules.
* Typical examples include global variables.
*/
abstract class EntryPoint extends string {
bindingset[this]
EntryPoint() { any() }
/** Gets a data-flow node that uses this entry point. */
abstract DataFlow::SourceNode getAUse();
/** Gets a data-flow node that defines this entry point. */
abstract DataFlow::Node getARhs();
}
/**
* Provides the actual implementation of API graphs, cached for performance.
*
* Ideally, we'd like nodes to correspond to (global) access paths, with edge labels
* corresponding to extending the access path by one element. We also want to be able to map
* nodes to their definitions and uses in the data-flow graph, and this should happen modulo
* (inter-procedural) data flow.
*
* This, however, is not easy to implement, since access paths can have unbounded length
* and we need some way of recognizing cycles to avoid non-termination. Unfortunately, expressing
* a condition like "this node hasn't been involved in constructing any predecessor of
* this node in the API graph" without negative recursion is tricky.
*
* So instead most nodes are directly associated with a data-flow node, representing
* either a use or a definition of an API component. This ensures that we only have a finite
* number of nodes. However, we can now have multiple nodes with the same access
* path, which are essentially indistinguishable for a client of the API.
*
* On the other hand, a single node can have multiple access paths (which is, of
* course, unavoidable). We pick as canonical the alphabetically least access path with
* shortest length.
*/
cached
private module Impl {
cached
newtype TApiNode =
MkRoot() or
MkModuleDef(string m) { exists(MkModuleExport(m)) } or
MkModuleUse(string m) { exists(MkModuleImport(m)) } or
MkModuleExport(string m) {
exists(Module mod | mod = importableModule(m) |
// exclude modules that don't actually export anything
exports(m, _)
or
exports(m, _, _)
or
exists(NodeModule nm | nm = mod |
exists(SSA::implicitInit([nm.getModuleVariable(), nm.getExportsVariable()]))
)
)
} or
MkModuleImport(string m) { imports(_, m) } or
MkClassInstance(DataFlow::ClassNode cls) { cls = trackDefNode(_) and hasSemantics(cls) } or
MkAsyncFuncResult(DataFlow::FunctionNode f) {
f = trackDefNode(_) and f.getFunction().isAsync() and hasSemantics(f)
} or
MkDef(DataFlow::Node nd) { rhs(_, _, nd) } or
MkUse(DataFlow::Node nd) { use(_, _, nd) } or
MkCanonicalNameDef(CanonicalName n) { isDefined(n) } or
MkCanonicalNameUse(CanonicalName n) { isUsed(n) }
class TDef = MkModuleDef or TNonModuleDef;
class TNonModuleDef =
MkModuleExport or MkClassInstance or MkAsyncFuncResult or MkDef or MkCanonicalNameDef;
class TUse = MkModuleUse or MkModuleImport or MkUse or MkCanonicalNameUse;
private predicate hasSemantics(DataFlow::Node nd) { not nd.getTopLevel().isExterns() }
/** Holds if `imp` is an import of module `m`. */
private predicate imports(DataFlow::Node imp, string m) {
imp = DataFlow::moduleImport(m) and
// path must not start with a dot or a slash
m.regexpMatch("[^./].*") and
hasSemantics(imp)
}
/** Gets the definition of module `m`. */
private Module importableModule(string m) {
exists(NPMPackage pkg, PackageJSON json |
json = pkg.getPackageJSON() and not json.isPrivate()
|
result = pkg.getMainModule() and
not result.isExterns() and
m = pkg.getPackageName()
)
}
private predicate isUsed(CanonicalName n) {
exists(n.(TypeName).getAnAccess()) or
exists(n.(Namespace).getAnAccess())
}
private predicate isDefined(CanonicalName n) {
exists(ASTNode def |
def = n.(TypeName).getADefinition() or
def = n.(Namespace).getADefinition()
|
not def.isAmbient()
)
}
/**
* Holds if `rhs` is the right-hand side of a definition of a node that should have an
* incoming edge from `base` labeled `lbl` in the API graph.
*/
cached
predicate rhs(TApiNode base, string lbl, DataFlow::Node rhs) {
hasSemantics(rhs) and
(
base = MkRoot() and
rhs = lbl.(EntryPoint).getARhs()
or
exists(string m, string prop |
base = MkModuleExport(m) and
lbl = Label::member(prop) and
exports(m, prop, rhs)
)
or
exists(DataFlow::Node def, DataFlow::SourceNode pred |
rhs(base, def) and pred = trackDefNode(def)
|
exists(DataFlow::PropWrite pw | pw = pred.getAPropertyWrite() |
lbl = Label::memberFromRef(pw) and
rhs = pw.getRhs()
)
or
exists(DataFlow::FunctionNode fn | fn = pred |
not fn.getFunction().isAsync() and
lbl = Label::return() and
rhs = fn.getAReturn()
)
or
lbl = Label::promised() and
PromiseFlow::storeStep(rhs, pred, Promises::valueProp())
)
or
exists(DataFlow::ClassNode cls, string name |
base = MkClassInstance(cls) and
lbl = Label::member(name) and
rhs = cls.getInstanceMethod(name)
)
or
exists(DataFlow::FunctionNode f |
base = MkAsyncFuncResult(f) and
lbl = Label::promised() and
rhs = f.getAReturn()
)
or
exists(DataFlow::SourceNode src, DataFlow::InvokeNode invk |
use(base, src) and invk = trackUseNode(src).getAnInvocation()
|
exists(int i |
lbl = Label::parameter(i) and
rhs = invk.getArgument(i)
)
or
lbl = Label::receiver() and
rhs = invk.(DataFlow::CallNode).getReceiver()
)
or
exists(DataFlow::SourceNode src, DataFlow::PropWrite pw |
use(base, src) and pw = trackUseNode(src).getAPropertyWrite() and rhs = pw.getRhs()
|
lbl = Label::memberFromRef(pw)
)
)
}
/**
* Holds if `rhs` is the right-hand side of a definition of node `nd`.
*/
cached
predicate rhs(TApiNode nd, DataFlow::Node rhs) {
exists(string m | nd = MkModuleExport(m) | exports(m, rhs))
or
nd = MkDef(rhs)
or
exists(CanonicalName n | nd = MkCanonicalNameDef(n) |
rhs = n.(Namespace).getADefinition().flow() or
rhs = n.(CanonicalFunctionName).getADefinition().flow()
)
}
/**
* Holds if `ref` is a use of a node that should have an incoming edge from `base` labeled
* `lbl` in the API graph.
*/
cached
predicate use(TApiNode base, string lbl, DataFlow::Node ref) {
hasSemantics(ref) and
(
base = MkRoot() and
ref = lbl.(EntryPoint).getAUse()
or
exists(DataFlow::SourceNode src, DataFlow::SourceNode pred |
use(base, src) and pred = trackUseNode(src)
|
// `module.exports` is special: it is a use of a def-node, not a use-node,
// so we want to exclude it here
(base instanceof TNonModuleDef or base instanceof TUse) and
lbl = Label::memberFromRef(ref) and
ref = pred.getAPropertyRead()
or
lbl = Label::instance() and
ref = pred.getAnInstantiation()
or
lbl = Label::return() and
ref = pred.getAnInvocation()
or
lbl = Label::promised() and
PromiseFlow::loadStep(pred, ref, Promises::valueProp())
)
or
exists(DataFlow::Node def, DataFlow::FunctionNode fn |
rhs(base, def) and fn = trackDefNode(def)
|
exists(int i |
lbl = Label::parameter(i) and
ref = fn.getParameter(i)
)
or
lbl = Label::receiver() and
ref = fn.getReceiver()
)
or
exists(DataFlow::Node def, DataFlow::ClassNode cls, int i |
rhs(base, def) and cls = trackDefNode(def)
|
lbl = Label::parameter(i) and
ref = cls.getConstructor().getParameter(i)
)
or
exists(TypeName tn |
base = MkCanonicalNameUse(tn) and
lbl = Label::instance() and
ref = getANodeWithType(tn)
)
)
}
/**
* Holds if `ref` is a use of node `nd`.
*/
cached
predicate use(TApiNode nd, DataFlow::Node ref) {
exists(string m, Module mod | nd = MkModuleDef(m) and mod = importableModule(m) |
ref = DataFlow::ssaDefinitionNode(SSA::implicitInit(mod.(NodeModule).getModuleVariable()))
or
ref = DataFlow::parameterNode(mod.(AmdModule).getDefine().getModuleParameter())
)
or
exists(string m, Module mod | nd = MkModuleExport(m) and mod = importableModule(m) |
ref = DataFlow::ssaDefinitionNode(SSA::implicitInit(mod.(NodeModule).getExportsVariable()))
or
ref = DataFlow::parameterNode(mod.(AmdModule).getDefine().getExportsParameter())
or
exists(DataFlow::Node base | use(MkModuleDef(m), base) |
ref = trackUseNode(base).getAPropertyRead("exports")
)
)
or
exists(string m |
nd = MkModuleImport(m) and
ref = DataFlow::moduleImport(m)
)
or
exists(DataFlow::ClassNode cls | nd = MkClassInstance(cls) | ref = cls.getAReceiverNode())
or
nd = MkUse(ref)
or
exists(CanonicalName n | nd = MkCanonicalNameUse(n) | ref.asExpr() = n.getAnAccess())
}
/** Holds if module `m` exports `rhs`. */
private predicate exports(string m, DataFlow::Node rhs) {
exists(Module mod | mod = importableModule(m) |
rhs = mod.(AmdModule).getDefine().getModuleExpr().flow()
or
exports(m, "default", rhs)
or
exists(ExportAssignDeclaration assgn | assgn.getTopLevel() = mod |
rhs = assgn.getExpression().flow()
)
or
rhs = mod.(Closure::ClosureModule).getExportsVariable().getAnAssignedExpr().flow()
)
}
/** Holds if module `m` exports `rhs` under the name `prop`. */
private predicate exports(string m, string prop, DataFlow::Node rhs) {
exists(ExportDeclaration exp | exp.getEnclosingModule() = importableModule(m) |
rhs = exp.getSourceNode(prop)
or
exists(Variable v |
exp.exportsAs(v, prop) and
rhs = v.getAnAssignedExpr().flow()
)
)
}
private DataFlow::SourceNode trackUseNode(DataFlow::SourceNode nd, DataFlow::TypeTracker t) {
t.start() and
use(_, nd) and
result = nd
or
exists(DataFlow::TypeTracker t2 | result = trackUseNode(nd, t2).track(t2, t))
}
/**
* Gets a node that is inter-procedurally reachable from `nd`, which is a use of some node.
*/
cached
DataFlow::SourceNode trackUseNode(DataFlow::SourceNode nd) {
result = trackUseNode(nd, DataFlow::TypeTracker::end())
}
private DataFlow::SourceNode trackDefNode(DataFlow::Node nd, DataFlow::TypeBackTracker t) {
t.start() and
rhs(_, nd) and
result = nd.getALocalSource()
or
exists(DataFlow::TypeBackTracker t2 | result = trackDefNode(nd, t2).backtrack(t2, t))
}
/**
* Gets a node that inter-procedurally flows into `nd`, which is a definition of some node.
*/
cached
DataFlow::SourceNode trackDefNode(DataFlow::Node nd) {
result = trackDefNode(nd, DataFlow::TypeBackTracker::end())
}
private DataFlow::SourceNode getANodeWithType(TypeName tn) {
exists(string moduleName, string typeName |
tn.hasQualifiedName(moduleName, typeName) and
result.hasUnderlyingType(moduleName, typeName)
)
}
/**
* Holds if there is an edge from `pred` to `succ` in the API graph that is labeled with `lbl`.
*/
cached
predicate edge(TApiNode pred, string lbl, TApiNode succ) {
exists(string m |
pred = MkRoot() and
lbl = Label::mod(m)
|
succ = MkModuleDef(m)
or
succ = MkModuleUse(m)
)
or
exists(string m |
pred = MkModuleDef(m) and
lbl = Label::member("exports") and
succ = MkModuleExport(m)
or
pred = MkModuleUse(m) and
lbl = Label::member("exports") and
succ = MkModuleImport(m)
)
or
exists(DataFlow::SourceNode ref |
use(pred, lbl, ref) and
succ = MkUse(ref)
)
or
exists(DataFlow::Node rhs |
rhs(pred, lbl, rhs) and
succ = MkDef(rhs)
)
or
exists(DataFlow::Node def |
rhs(pred, def) and
lbl = Label::instance() and
succ = MkClassInstance(trackDefNode(def))
)
or
exists(CanonicalName cn |
pred = MkRoot() and
lbl = Label::mod(cn.getExternalModuleName())
|
succ = MkCanonicalNameUse(cn) or
succ = MkCanonicalNameDef(cn)
)
or
exists(CanonicalName cn1, CanonicalName cn2 |
cn2 = cn1.getAChild() and
lbl = Label::member(cn2.getName())
|
(pred = MkCanonicalNameDef(cn1) or pred = MkCanonicalNameUse(cn1)) and
(succ = MkCanonicalNameDef(cn2) or succ = MkCanonicalNameUse(cn2))
)
or
exists(DataFlow::Node nd, DataFlow::FunctionNode f |
pred = MkDef(nd) and
f = trackDefNode(nd) and
lbl = Label::return() and
succ = MkAsyncFuncResult(f)
)
}
/**
* Holds if there is an edge from `pred` to `succ` in the API graph.
*/
private predicate edge(TApiNode pred, TApiNode succ) { edge(pred, _, succ) }
/** Gets the shortest distance from the root to `nd` in the API graph. */
cached
int distanceFromRoot(TApiNode nd) = shortestDistances(MkRoot/0, edge/2)(_, nd, result)
}
import Label as EdgeLabel
}
private module Label {
/** Gets the edge label for the module `m`. */
bindingset[m]
bindingset[result]
string mod(string m) { result = "module " + m }
/** Gets the `member` edge label for member `m`. */
bindingset[m]
bindingset[result]
string member(string m) { result = "member " + m }
/** Gets the `member` edge label for the unknown member. */
string unknownMember() { result = "member *" }
/** Gets the `member` edge label for the given property reference. */
string memberFromRef(DataFlow::PropRef pr) {
exists(string pn | pn = pr.getPropertyName() |
result = member(pn) and
// only consider properties with alphanumeric(-ish) names, excluding special properties
// and properties whose names look like they are meant to be internal
pn.regexpMatch("(?!prototype$|__)[a-zA-Z_$][\\w\\-.$]*")
)
or
not exists(pr.getPropertyName()) and
result = unknownMember()
}
/** Gets the `instance` edge label. */
string instance() { result = "instance" }
/**
* Gets the `parameter` edge label for the parameter `s`.
*
* This is an internal helper predicate; use `parameter` instead.
*/
bindingset[result]
bindingset[s]
string parameterByStringIndex(string s) {
result = "parameter " + s and
s.toInt() >= 0
}
/** Gets the `parameter` edge label for the `i`th parameter. */
bindingset[i]
string parameter(int i) { result = parameterByStringIndex(i.toString()) }
/** Gets the `parameter` edge label for the receiver. */
string receiver() { result = "parameter -1" }
/** Gets the `return` edge label. */
string return() { result = "return" }
/** Gets the `promised` edge label connecting a promise to its contained value. */
string promised() { result = "promised" }
}
/**
* A CommonJS `module` or `exports` variable, considered as a source node.
*/
private class AdditionalSourceNode extends DataFlow::SourceNode::Range {
AdditionalSourceNode() {
exists(NodeModule m, Variable v |
v in [m.getModuleVariable(), m.getExportsVariable()] and
this = DataFlow::ssaDefinitionNode(SSA::implicitInit(v))
)
}
}

View File

@@ -28,42 +28,29 @@ module SQL {
* Provides classes modelling the (API compatible) `mysql` and `mysql2` packages.
*/
private module MySql {
private DataFlow::SourceNode mysql() { result = DataFlow::moduleImport(["mysql", "mysql2"]) }
private DataFlow::CallNode createPool() { result = mysql().getAMemberCall("createPool") }
/** Gets a reference to a MySQL pool. */
private DataFlow::SourceNode pool(DataFlow::TypeTracker t) {
t.start() and
result = createPool()
or
exists(DataFlow::TypeTracker t2 | result = pool(t2).track(t2, t))
}
/** Gets a reference to a MySQL pool. */
private DataFlow::SourceNode pool() { result = pool(DataFlow::TypeTracker::end()) }
/** Gets the package name `mysql` or `mysql2`. */
API::Node mysql() { result = API::moduleImport(["mysql", "mysql2"]) }
/** Gets a call to `mysql.createConnection`. */
DataFlow::CallNode createConnection() { result = mysql().getAMemberCall("createConnection") }
API::Node createConnection() { result = mysql().getMember("createConnection").getReturn() }
/** Gets a reference to a MySQL connection instance. */
private DataFlow::SourceNode connection(DataFlow::TypeTracker t) {
t.start() and
(
result = createConnection()
or
result = pool().getAMethodCall("getConnection").getABoundCallbackParameter(0, 1)
)
/** Gets a call to `mysql.createPool`. */
API::Node createPool() { result = mysql().getMember("createPool").getReturn() }
/** Gets a data flow node that contains a freshly created MySQL connection instance. */
API::Node connection() {
result = createConnection()
or
exists(DataFlow::TypeTracker t2 | result = connection(t2).track(t2, t))
result = createPool().getMember("getConnection").getParameter(0).getParameter(1)
}
/** Gets a reference to a MySQL connection instance. */
DataFlow::SourceNode connection() { result = connection(DataFlow::TypeTracker::end()) }
/** A call to the MySql `query` method. */
private class QueryCall extends DatabaseAccess, DataFlow::MethodCallNode {
QueryCall() { this = [pool(), connection()].getAMethodCall("query") }
QueryCall() {
exists(API::Node recv | recv = createPool() or recv = connection() |
this = recv.getMember("query").getReturn().getAUse()
)
}
override DataFlow::Node getAQueryArgument() { result = getArgument(0) }
}
@@ -76,7 +63,12 @@ private module MySql {
/** A call to the `escape` or `escapeId` method that performs SQL sanitization. */
class EscapingSanitizer extends SQL::SqlSanitizer, MethodCallExpr {
EscapingSanitizer() {
this = [mysql(), pool(), connection()].getAMethodCall(["escape", "escapeId"]).asExpr() and
this =
[mysql(), createPool(), connection()]
.getMember(["escape", "escapeId"])
.getReturn()
.getAUse()
.asExpr() and
input = this.getArgument(0) and
output = this
}
@@ -87,8 +79,9 @@ private module MySql {
string kind;
Credentials() {
exists(string prop |
this = [createConnection(), createPool()].getOptionArgument(0, prop).asExpr() and
exists(API::Node call, string prop |
call in [createConnection(), createPool()] and
call.getAUse().asExpr().(CallExpr).hasOptionArgument(0, prop, this) and
(
prop = "user" and kind = "user name"
or
@@ -105,49 +98,29 @@ private module MySql {
* Provides classes modelling the `pg` package.
*/
private module Postgres {
/** Gets an expression of the form `new require('pg').Client()`. */
API::Node newClient() { result = API::moduleImport("pg").getMember("Client").getInstance() }
/** Gets a data flow node that holds a freshly created Postgres client instance. */
API::Node client() {
result = newClient()
or
// pool.connect(function(err, client) { ... })
result = newPool().getMember("connect").getParameter(0).getParameter(1)
}
/** Gets an expression that constructs a new connection pool. */
DataFlow::InvokeNode newPool() {
API::Node newPool() {
// new require('pg').Pool()
result = DataFlow::moduleImport("pg").getAConstructorInvocation("Pool")
result = API::moduleImport("pg").getMember("Pool").getInstance()
or
// new require('pg-pool')
result = DataFlow::moduleImport("pg-pool").getAnInstantiation()
result = API::moduleImport("pg-pool").getInstance()
}
/** Gets a data flow node referring to a connection pool. */
private DataFlow::SourceNode pool(DataFlow::TypeTracker t) {
t.start() and
result = newPool()
or
exists(DataFlow::TypeTracker t2 | result = pool(t2).track(t2, t))
}
/** Gets a data flow node referring to a connection pool. */
DataFlow::SourceNode pool() { result = pool(DataFlow::TypeTracker::end()) }
/** Gets a creation of a Postgres client. */
DataFlow::InvokeNode newClient() {
result = DataFlow::moduleImport("pg").getAConstructorInvocation("Client")
}
/** Gets a data flow node referring to a Postgres client. */
private DataFlow::SourceNode client(DataFlow::TypeTracker t) {
t.start() and
(
result = newClient()
or
result = pool().getAMethodCall("connect").getABoundCallbackParameter(0, 1)
)
or
exists(DataFlow::TypeTracker t2 | result = client(t2).track(t2, t))
}
/** Gets a data flow node referring to a Postgres client. */
DataFlow::SourceNode client() { result = client(DataFlow::TypeTracker::end()) }
/** A call to the Postgres `query` method. */
private class QueryCall extends DatabaseAccess, DataFlow::MethodCallNode {
QueryCall() { this = [client(), pool()].getAMethodCall("query") }
QueryCall() { this = [client(), newPool()].getMember("query").getReturn().getAUse() }
override DataFlow::Node getAQueryArgument() { result = getArgument(0) }
}
@@ -162,10 +135,14 @@ private module Postgres {
string kind;
Credentials() {
exists(string prop | this = [newClient(), newPool()].getOptionArgument(0, prop).asExpr() |
prop = "user" and kind = "user name"
or
prop = "password" and kind = prop
exists(DataFlow::InvokeNode call, string prop |
call = [client(), newPool()].getAUse() and
this = call.getOptionArgument(0, prop).asExpr() and
(
prop = "user" and kind = "user name"
or
prop = "password" and kind = prop
)
)
}
@@ -178,29 +155,18 @@ private module Postgres {
*/
private module Sqlite {
/** Gets a reference to the `sqlite3` module. */
DataFlow::SourceNode sqlite() {
result = DataFlow::moduleImport("sqlite3")
API::Node sqlite() {
result = API::moduleImport("sqlite3")
or
result = sqlite().getAMemberCall("verbose")
result = sqlite().getMember("verbose").getReturn()
}
/** Gets an expression that constructs a Sqlite database instance. */
DataFlow::SourceNode newDb() {
API::Node newDb() {
// new require('sqlite3').Database()
result = sqlite().getAConstructorInvocation("Database")
result = sqlite().getMember("Database").getInstance()
}
/** Gets a data flow node referring to a Sqlite database instance. */
private DataFlow::SourceNode db(DataFlow::TypeTracker t) {
t.start() and
result = newDb()
or
exists(DataFlow::TypeTracker t2 | result = db(t2).track(t2, t))
}
/** Gets a data flow node referring to a Sqlite database instance. */
DataFlow::SourceNode db() { result = db(DataFlow::TypeTracker::end()) }
/** A call to a Sqlite query method. */
private class QueryCall extends DatabaseAccess, DataFlow::MethodCallNode {
QueryCall() {
@@ -212,7 +178,7 @@ private module Sqlite {
meth = "prepare" or
meth = "run"
|
this = db().getAMethodCall(meth)
this = newDb().getMember(meth).getReturn().getAUse()
)
}
@@ -230,30 +196,24 @@ private module Sqlite {
*/
private module MsSql {
/** Gets a reference to the `mssql` module. */
DataFlow::SourceNode mssql() { result = DataFlow::moduleImport("mssql") }
API::Node mssql() { result = API::moduleImport("mssql") }
/** Gets a data flow node referring to a request object. */
private DataFlow::SourceNode request(DataFlow::TypeTracker t) {
t.start() and
(
// new require('mssql').Request()
result = mssql().getAConstructorInvocation("Request")
or
// request.input(...)
result = request().getAMethodCall("input")
)
/** Gets an expression that creates a request object. */
API::Node request() {
// new require('mssql').Request()
result = mssql().getMember("Request").getInstance()
or
exists(DataFlow::TypeTracker t2 | result = request(t2).track(t2, t))
// request.input(...)
result = request().getMember("input").getReturn()
}
/** Gets a data flow node referring to a request object. */
DataFlow::SourceNode request() { result = request(DataFlow::TypeTracker::end()) }
/** A tagged template evaluated as a query. */
private class QueryTemplateExpr extends DatabaseAccess, DataFlow::ValueNode {
override TaggedTemplateExpr astNode;
QueryTemplateExpr() { mssql().getAPropertyRead("query").flowsToExpr(astNode.getTag()) }
QueryTemplateExpr() {
mssql().getMember("query").getAUse() = DataFlow::valueNode(astNode.getTag())
}
override DataFlow::Node getAQueryArgument() {
result = DataFlow::valueNode(astNode.getTemplate().getAnElement())
@@ -262,7 +222,7 @@ private module MsSql {
/** A call to a MsSql query method. */
private class QueryCall extends DatabaseAccess, DataFlow::MethodCallNode {
QueryCall() { this = request().getAMethodCall(["query", "batch"]) }
QueryCall() { this = request().getMember(["query", "batch"]).getReturn().getAUse() }
override DataFlow::Node getAQueryArgument() { result = getArgument(0) }
}
@@ -292,9 +252,9 @@ private module MsSql {
Credentials() {
exists(DataFlow::InvokeNode call, string prop |
(
call = mssql().getAMemberCall("connect")
call = mssql().getMember("connect").getReturn().getAUse()
or
call = mssql().getAConstructorInvocation("ConnectionPool")
call = mssql().getMember("ConnectionPool").getInstance().getAUse()
) and
this = call.getOptionArgument(0, prop).asExpr() and
(
@@ -313,26 +273,17 @@ private module MsSql {
* Provides classes modelling the `sequelize` package.
*/
private module Sequelize {
/** Gets a node referring to an instance of the `Sequelize` class. */
private DataFlow::SourceNode sequelize(DataFlow::TypeTracker t) {
t.start() and
result = DataFlow::moduleImport("sequelize").getAnInstantiation()
or
exists(DataFlow::TypeTracker t2 | result = sequelize(t2).track(t2, t))
}
/** Gets an import of the `sequelize` module. */
API::Node sequelize() { result = API::moduleImport("sequelize") }
/** Gets a node referring to an instance of the `Sequelize` class. */
DataFlow::SourceNode sequelize() { result = sequelize(DataFlow::TypeTracker::end()) }
/** Gets an expression that creates an instance of the `Sequelize` class. */
API::Node newSequelize() { result = sequelize().getInstance() }
/** A call to `Sequelize.query`. */
private class QueryCall extends DatabaseAccess, DataFlow::ValueNode {
override MethodCallExpr astNode;
private class QueryCall extends DatabaseAccess, DataFlow::MethodCallNode {
QueryCall() { this = newSequelize().getMember("query").getReturn().getAUse() }
QueryCall() { this = sequelize().getAMethodCall("query") }
override DataFlow::Node getAQueryArgument() {
result = DataFlow::valueNode(astNode.getArgument(0))
}
override DataFlow::Node getAQueryArgument() { result = getArgument(0) }
}
/** An expression that is passed to `Sequelize.query` method and hence interpreted as SQL. */
@@ -349,7 +300,7 @@ private module Sequelize {
Credentials() {
exists(NewExpr ne, string prop |
ne = sequelize().asExpr() and
ne = newSequelize().getAUse().asExpr() and
(
this = ne.getArgument(1) and prop = "username"
or
@@ -376,69 +327,36 @@ private module Spanner {
/**
* Gets a node that refers to the `Spanner` class
*/
DataFlow::SourceNode spanner() {
API::Node spanner() {
// older versions
result = DataFlow::moduleImport("@google-cloud/spanner")
result = API::moduleImport("@google-cloud/spanner")
or
// newer versions
result = DataFlow::moduleMember("@google-cloud/spanner", "Spanner")
result = API::moduleImport("@google-cloud/spanner").getMember("Spanner")
}
/** Gets a data flow node referring to the result of `Spanner()` or `new Spanner()`. */
private DataFlow::SourceNode spannerNew(DataFlow::TypeTracker t) {
t.start() and
result = spanner().getAnInvocation()
or
exists(DataFlow::TypeTracker t2 | result = spannerNew(t2).track(t2, t))
/**
* Gets a node that refers to an instance of the `Database` class.
*/
API::Node database() {
result =
spanner().getReturn().getMember("instance").getReturn().getMember("database").getReturn()
}
/** Gets a data flow node referring to the result of `Spanner()` or `new Spanner()`. */
DataFlow::SourceNode spannerNew() { result = spannerNew(DataFlow::TypeTracker::end()) }
/** Gets a data flow node referring to the result of `.instance()`. */
private DataFlow::SourceNode instance(DataFlow::TypeTracker t) {
t.start() and
result = spannerNew().getAMethodCall("instance")
or
exists(DataFlow::TypeTracker t2 | result = instance(t2).track(t2, t))
/**
* Gets a node that refers to an instance of the `v1.SpannerClient` class.
*/
API::Node v1SpannerClient() {
result = spanner().getMember("v1").getMember("SpannerClient").getInstance()
}
/** Gets a data flow node referring to the result of `.instance()`. */
DataFlow::SourceNode instance() { result = instance(DataFlow::TypeTracker::end()) }
/** Gets a node that refers to an instance of the `Database` class. */
private DataFlow::SourceNode database(DataFlow::TypeTracker t) {
t.start() and
result = instance().getAMethodCall("database")
or
exists(DataFlow::TypeTracker t2 | result = database(t2).track(t2, t))
/**
* Gets a node that refers to a transaction object.
*/
API::Node transaction() {
result = database().getMember("runTransaction").getParameter(0).getParameter(1)
}
/** Gets a node that refers to an instance of the `Database` class. */
DataFlow::SourceNode database() { result = database(DataFlow::TypeTracker::end()) }
/** Gets a node that refers to an instance of the `v1.SpannerClient` class. */
private DataFlow::SourceNode v1SpannerClient(DataFlow::TypeTracker t) {
t.start() and
result = spanner().getAPropertyRead("v1").getAPropertyRead("SpannerClient").getAnInstantiation()
or
exists(DataFlow::TypeTracker t2 | result = v1SpannerClient(t2).track(t2, t))
}
/** Gets a node that refers to an instance of the `v1.SpannerClient` class. */
DataFlow::SourceNode v1SpannerClient() { result = v1SpannerClient(DataFlow::TypeTracker::end()) }
/** Gets a node that refers to a transaction object. */
private DataFlow::SourceNode transaction(DataFlow::TypeTracker t) {
t.start() and
result = database().getAMethodCall("runTransaction").getABoundCallbackParameter(0, 1)
or
exists(DataFlow::TypeTracker t2 | result = transaction(t2).track(t2, t))
}
/** Gets a node that refers to a transaction object. */
DataFlow::SourceNode transaction() { result = transaction(DataFlow::TypeTracker::end()) }
/**
* A call to a Spanner method that executes a SQL query.
*/
@@ -460,7 +378,8 @@ private module Spanner {
*/
class DatabaseRunCall extends SqlExecution {
DatabaseRunCall() {
this = database().getAMethodCall(["run", "runPartitionedUpdate", "runStream"])
this =
database().getMember(["run", "runPartitionedUpdate", "runStream"]).getReturn().getAUse()
}
}
@@ -468,7 +387,9 @@ private module Spanner {
* A call to `Transaction.run`, `Transaction.runStream` or `Transaction.runUpdate`.
*/
class TransactionRunCall extends SqlExecution {
TransactionRunCall() { this = transaction().getAMethodCall(["run", "runStream", "runUpdate"]) }
TransactionRunCall() {
this = transaction().getMember(["run", "runStream", "runUpdate"]).getReturn().getAUse()
}
}
/**
@@ -476,7 +397,8 @@ private module Spanner {
*/
class ExecuteSqlCall extends SqlExecution {
ExecuteSqlCall() {
this = v1SpannerClient().getAMethodCall(["executeSql", "executeStreamingSql"])
this =
v1SpannerClient().getMember(["executeSql", "executeStreamingSql"]).getReturn().getAUse()
}
override DataFlow::Node getAQueryArgument() {

View File

@@ -5,6 +5,50 @@
import javascript
private predicate execApi(string mod, string fn, int cmdArg, int optionsArg, boolean shell) {
mod = "cross-spawn" and
fn = "sync" and
cmdArg = 0 and
shell = false and
optionsArg = -1
or
mod = "execa" and
optionsArg = -1 and
(
shell = false and
(
fn = "node" or
fn = "shell" or
fn = "shellSync" or
fn = "stdout" or
fn = "stderr" or
fn = "sync"
)
or
shell = true and
(fn = "command" or fn = "commandSync")
) and
cmdArg = 0
}
private predicate execApi(string mod, int cmdArg, int optionsArg, boolean shell) {
shell = false and
(
mod = "cross-spawn" and cmdArg = 0 and optionsArg = -1
or
mod = "cross-spawn-async" and cmdArg = 0 and optionsArg = -1
or
mod = "exec-async" and cmdArg = 0 and optionsArg = -1
or
mod = "execa" and cmdArg = 0 and optionsArg = -1
)
or
shell = true and
mod = "exec" and
optionsArg = -2 and
cmdArg = 0
}
private class SystemCommandExecutors extends SystemCommandExecution, DataFlow::InvokeNode {
int cmdArg;
int optionsArg; // either a positive number representing the n'th argument, or a negative number representing the n'th last argument (e.g. -2 is the second last argument).
@@ -12,61 +56,19 @@ private class SystemCommandExecutors extends SystemCommandExecution, DataFlow::I
boolean sync;
SystemCommandExecutors() {
exists(string mod, DataFlow::SourceNode callee |
exists(string method |
mod = "cross-spawn" and method = "sync" and cmdArg = 0 and shell = false and optionsArg = -1
or
mod = "execa" and
optionsArg = -1 and
(
shell = false and
(
method = "shell" or
method = "shellSync" or
method = "stdout" or
method = "stderr" or
method = "sync"
)
or
shell = true and
(method = "command" or method = "commandSync")
) and
cmdArg = 0
or
mod = "execa" and
method = "node" and
cmdArg = 0 and
optionsArg = 1 and
shell = false
|
callee = DataFlow::moduleMember(mod, method) and
sync = getSync(method)
exists(string mod |
exists(string fn |
execApi(mod, fn, cmdArg, optionsArg, shell) and
sync = getSync(fn) and
this = API::moduleImport(mod).getMember(fn).getReturn().getAUse()
)
or
execApi(mod, cmdArg, optionsArg, shell) and
sync = false and
(
shell = false and
(
mod = "cross-spawn" and cmdArg = 0 and optionsArg = -1
or
mod = "cross-spawn-async" and cmdArg = 0 and optionsArg = -1
or
mod = "exec-async" and cmdArg = 0 and optionsArg = -1
or
mod = "execa" and cmdArg = 0 and optionsArg = -1
)
or
shell = true and
mod = "exec" and
optionsArg = -2 and
cmdArg = 0
) and
callee = DataFlow::moduleImport(mod)
|
this = callee.getACall()
this = API::moduleImport(mod).getReturn().getAUse()
)
or
this = DataFlow::moduleImport("foreground-child").getACall() and
this = API::moduleImport("foreground-child").getReturn().getAUse() and
cmdArg = 0 and
optionsArg = 1 and
shell = false and
@@ -110,19 +112,19 @@ private class RemoteCommandExecutor extends SystemCommandExecution, DataFlow::In
int cmdArg;
RemoteCommandExecutor() {
this = DataFlow::moduleImport("remote-exec").getACall() and
this = API::moduleImport("remote-exec").getReturn().getAUse() and
cmdArg = 1
or
exists(DataFlow::SourceNode ssh2, DataFlow::SourceNode client |
ssh2 = DataFlow::moduleImport("ssh2") and
(client = ssh2 or client = ssh2.getAPropertyRead("Client")) and
this = client.getAnInstantiation().getAMethodCall("exec") and
exists(API::Node ssh2, API::Node client |
ssh2 = API::moduleImport("ssh2") and
client in [ssh2, ssh2.getMember("Client")] and
this = client.getInstance().getMember("exec").getReturn().getAUse() and
cmdArg = 0
)
or
exists(DataFlow::SourceNode ssh2stream |
ssh2stream = DataFlow::moduleMember("ssh2-streams", "SSH2Stream") and
this = ssh2stream.getAnInstantiation().getAMethodCall("exec") and
exists(API::Node ssh2stream |
ssh2stream = API::moduleImport("ssh2-streams").getMember("SSH2Stream") and
this = ssh2stream.getInstance().getMember("exec").getReturn().getAUse() and
cmdArg = 1
)
}
@@ -137,7 +139,7 @@ private class RemoteCommandExecutor extends SystemCommandExecution, DataFlow::In
}
private class Opener extends SystemCommandExecution, DataFlow::InvokeNode {
Opener() { this = DataFlow::moduleImport("opener").getACall() }
Opener() { this = API::moduleImport("opener").getReturn().getAUse() }
override DataFlow::Node getACommandArgument() { result = getOptionArgument(1, "command") }

View File

@@ -126,7 +126,7 @@ abstract class RateLimiter extends Express::RouteHandlerExpr { }
*/
class ExpressRateLimit extends RateLimiter {
ExpressRateLimit() {
DataFlow::moduleImport("express-rate-limit").getAnInvocation().flowsToExpr(this)
this = API::moduleImport("express-rate-limit").getReturn().getAUse().asExpr()
}
}
@@ -135,11 +135,7 @@ class ExpressRateLimit extends RateLimiter {
*/
class BruteForceRateLimit extends RateLimiter {
BruteForceRateLimit() {
exists(DataFlow::ModuleImportNode expressBrute, DataFlow::SourceNode prevent |
expressBrute.getPath() = "express-brute" and
prevent = expressBrute.getAnInstantiation().getAPropertyRead("prevent") and
prevent.flowsToExpr(this)
)
this = API::moduleImport("express-brute").getInstance().getMember("prevent").getAUse().asExpr()
}
}
@@ -148,11 +144,8 @@ class BruteForceRateLimit extends RateLimiter {
*/
class RouteHandlerLimitedByExpressLimiter extends RateLimitedRouteHandlerExpr {
RouteHandlerLimitedByExpressLimiter() {
exists(DataFlow::ModuleImportNode expressLimiter |
expressLimiter.getPath() = "express-limiter" and
expressLimiter.getACall().getArgument(0).getALocalSource().asExpr() =
this.getSetup().getRouter()
)
API::moduleImport("express-limiter").getParameter(0).getARhs().getALocalSource().asExpr() =
this.getSetup().getRouter()
}
}
@@ -175,14 +168,14 @@ class RouteHandlerLimitedByExpressLimiter extends RateLimitedRouteHandlerExpr {
class RateLimiterFlexibleRateLimiter extends DataFlow::FunctionNode {
RateLimiterFlexibleRateLimiter() {
exists(
string rateLimiterClassName, DataFlow::SourceNode rateLimiterClass,
DataFlow::SourceNode rateLimiterInstance, DataFlow::ParameterNode request
string rateLimiterClassName, API::Node rateLimiterClass, API::Node rateLimiterConsume,
DataFlow::ParameterNode request
|
rateLimiterClassName.matches("RateLimiter%") and
rateLimiterClass = DataFlow::moduleMember("rate-limiter-flexible", rateLimiterClassName) and
rateLimiterInstance = rateLimiterClass.getAnInstantiation() and
rateLimiterClass = API::moduleImport("rate-limiter-flexible").getMember(rateLimiterClassName) and
rateLimiterConsume = rateLimiterClass.getInstance().getMember("consume") and
request.getParameter() = getRouteHandlerParameter(getFunction(), "request") and
request.getAPropertyRead() = rateLimiterInstance.getAMemberCall("consume").getAnArgument()
request.getAPropertyRead().flowsTo(rateLimiterConsume.getAParameter().getARhs())
)
}
}

View File

@@ -0,0 +1,117 @@
/**
* 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.
*
* We also support negative assertions of the form `!def <path>` or `!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
* under normal circumstances.
*
* Note that this query file isn't itself meant to be run as a test; instead, the `.qlref`s
* referring to it from inside the individual test directories should be run. However, when
* all tests are run this test will also be run, hence we need to check in a (somewhat nonsensical)
* `.expected` file for it as well.
*/
import javascript
private DataFlow::Node getNode(API::Node nd, string kind) {
kind = "def" and
result = nd.getARhs()
or
kind = "use" and
result = nd.getAUse()
}
private string getLoc(DataFlow::Node nd) {
exists(string filepath, int startline |
nd.hasLocationInfo(filepath, startline, _, _, _) and
result = filepath + ":" + startline
)
}
/**
* An assertion matching a data-flow node against an API-graph feature.
*/
class Assertion extends Comment {
string polarity;
string expectedKind;
string expectedLoc;
Assertion() {
exists(string txt, string rex |
txt = this.getText().trim() and
rex = "(!?)(def|use) .*"
|
polarity = txt.regexpCapture(rex, 1) and
expectedKind = txt.regexpCapture(rex, 2) and
expectedLoc = getFile().getAbsolutePath() + ":" + getLocation().getStartLine()
)
}
string getEdgeLabel(int i) { result = this.getText().regexpFind("(?<=\\()[^()]+", i, _).trim() }
int getPathLength() { result = max(int i | exists(getEdgeLabel(i))) + 1 }
API::Node lookup(int i) {
i = getPathLength() and
result = API::root()
or
result = lookup(i + 1).getASuccessor(getEdgeLabel(i))
}
predicate isNegative() { polarity = "!" }
predicate holds() { getLoc(getNode(lookup(0), expectedKind)) = expectedLoc }
string tryExplainFailure() {
exists(int i, API::Node nd, string prefix, string suffix |
nd = lookup(i) and
i > 0 and
not exists(lookup([0 .. i - 1])) and
prefix = nd + " has no outgoing edge labelled " + getEdgeLabel(i - 1) + ";" and
if exists(nd.getASuccessor())
then
suffix =
"it does have outgoing edges labelled " +
concat(string lbl | exists(nd.getASuccessor(lbl)) | lbl, ", ") + "."
else suffix = "it has no outgoing edges at all."
|
result = prefix + " " + suffix
)
or
exists(API::Node nd, string kind | nd = lookup(0) |
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(lookup(0), expectedKind) |
not getLoc(nd) = expectedLoc and
result = "Node not found on this line (but there is one on line " + min(getLoc(nd)) + ")."
)
}
string explainFailure() {
if isNegative()
then (
holds() and
result = "Negative assertion failed."
) else (
not holds() and
(
result = tryExplainFailure()
or
not exists(tryExplainFailure()) and
result = "Positive assertion failed for unknown reasons."
)
)
}
}
query predicate failed(Assertion a, string explanation) { explanation = a.explainFailure() }

View File

@@ -0,0 +1 @@
import ApiGraphs.VerifyAssertions

View File

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

View File

@@ -0,0 +1,3 @@
{
"name": "argprops"
}

View File

@@ -0,0 +1 @@
import ApiGraphs.VerifyAssertions

View File

@@ -0,0 +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))))) */
};

View File

@@ -0,0 +1,6 @@
{
"name": "async-await",
"dependencies": {
"fs-extra": "*"
}
}

View File

@@ -0,0 +1 @@
import ApiGraphs.VerifyAssertions

View File

@@ -0,0 +1,7 @@
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))))) */
};

View File

@@ -0,0 +1,3 @@
{
"name": "branching-flow"
}

View File

@@ -0,0 +1 @@
import ApiGraphs.VerifyAssertions

View File

@@ -0,0 +1,23 @@
const util = require('util');
const EventEmitter = require('events');
function MyStream() {
EventEmitter.call(this);
}
util.inherits(MyStream, EventEmitter);
MyStream.prototype.write = (data) => this.emit('data', data);
function MyOtherStream() { /* use (instance (member MyOtherStream (member exports (module classes)))) */
EventEmitter.call(this);
}
util.inherits(MyOtherStream, EventEmitter);
MyOtherStream.prototype.write = function (data) { /* use (instance (member MyOtherStream (member exports (module classes)))) */
this.emit('data', data);
return this;
};
module.exports.MyOtherStream = MyOtherStream;

View File

@@ -0,0 +1,4 @@
{
"name": "classes",
"main": "./classes.js"
}

View File

@@ -0,0 +1 @@
import ApiGraphs.VerifyAssertions

View File

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

View File

@@ -0,0 +1,3 @@
{
"name": "ctor-arg"
}

View File

@@ -0,0 +1,9 @@
class CustomEntryPoint extends API::EntryPoint {
CustomEntryPoint() { this = "CustomEntryPoint" }
override DataFlow::SourceNode getAUse() { result = DataFlow::globalVarRef("CustomEntryPoint") }
override DataFlow::Node getARhs() { none() }
}
import ApiGraphs.VerifyAssertions

View File

@@ -0,0 +1 @@
module.exports = CustomEntryPoint.foo; /* use (member foo (CustomEntryPoint)) */

View File

@@ -0,0 +1,3 @@
{
"name": "custom-entry-point"
}

View File

@@ -0,0 +1 @@
import ApiGraphs.VerifyAssertions

View File

@@ -0,0 +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)))) */

View File

@@ -0,0 +1,6 @@
{
"name": "cyclic",
"dependencies": {
"foo": "*"
}
}

View File

@@ -0,0 +1 @@
import ApiGraphs.VerifyAssertions

View File

@@ -0,0 +1,5 @@
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))))) */

View File

@@ -0,0 +1,6 @@
{
"name": "dynamic-prop-read",
"dependencies": {
"classes": "*"
}
}

View File

@@ -0,0 +1 @@
import ApiGraphs.VerifyAssertions

View File

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

View File

@@ -0,0 +1,3 @@
{
"name": "imprecise-export"
}

View File

@@ -0,0 +1 @@
import ApiGraphs.VerifyAssertions

View File

@@ -0,0 +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))))))) */
clientSocket, head) => { /* ... */ });
req.on('information', (
info /* use (parameter 0 (parameter 1 (member on (return (member get (member exports (module http))))))) */
) => { /* ... */ });
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)))))))) */;

View File

@@ -0,0 +1,3 @@
{
"name": "imprecision"
}

View File

@@ -0,0 +1 @@
import ApiGraphs.VerifyAssertions

View File

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

View File

@@ -0,0 +1,6 @@
{
"name": "@myorg/mypkg",
"dependencies": {
"@myorg/myotherpkg": "*"
}
}

View File

@@ -0,0 +1 @@
import ApiGraphs.VerifyAssertions

View File

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

View File

@@ -0,0 +1,3 @@
{
"name": "nested-property-export"
}

View File

@@ -0,0 +1 @@
import ApiGraphs.VerifyAssertions

View File

@@ -0,0 +1,15 @@
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))))) */
);
function makeApp() {
return new express();
}
var app2 = makeApp();
app2.get('/',
(req, res) => res.send('Hello World!') /* def (parameter 1 (member get (instance (member exports (module express))))) */
);

View File

@@ -0,0 +1,6 @@
{
"name": "nonlocal",
"dependencies": {
"express": "*"
}
}

View File

@@ -0,0 +1 @@
import ApiGraphs.VerifyAssertions

View File

@@ -0,0 +1,21 @@
const fs = require("fs"),
fse = require("fs-extra"),
base64 = require("base-64");
module.exports.readFile = function (f) {
return new Promise((res, rej) => {
fs.readFile(f, (err, data) => {
if (err)
rej(err);
else
res(data); /* def (promised (return (member readFile (member exports (module promises))))) */
});
});
};
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))))) */
);
};

View File

@@ -0,0 +1,6 @@
{
"name": "promises",
"dependencies": {
"fs-extra": "*"
}
}

View File

@@ -0,0 +1 @@
| lib/utils.js:1:38:1:120 | /* use ... )))) */ | def (member util (member exports (module reexport))) has no outgoing edge labelled member id; it has no outgoing edges at all. |

View File

@@ -0,0 +1 @@
import ApiGraphs.VerifyAssertions

View File

@@ -0,0 +1,6 @@
const impl = require("./lib/impl.js");
module.exports = {
impl,
util: require("./lib/utils")
};

View File

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

View File

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

View File

@@ -0,0 +1,3 @@
{
"name": "reexport"
}

View File

@@ -0,0 +1 @@
import ApiGraphs.VerifyAssertions

View File

@@ -0,0 +1,6 @@
export class A {
foo() {
return this; /* def (return (member foo (instance (member A (member exports (module return-self)))))) */
}
bar(x) { } /* use (parameter 0 (member bar (instance (member A (member exports (module return-self)))))) */
}

View File

@@ -0,0 +1,3 @@
{
"name": "return-self"
}

View File

@@ -0,0 +1 @@
import ApiGraphs.VerifyAssertions

View File

@@ -0,0 +1,24 @@
import * as mongodb from "mongodb";
const express = require("express") as any;
const bodyParser = require("body-parser") as any;
declare function getCollection(): mongodb.Collection;
let app = express();
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 (module mongodb)))) */
});
import * as mongoose from "mongoose";
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 (module mongoose))))) */
getMongooseQuery().find({ id: v }); /* def (parameter 0 (member find (instance (member Query (module mongoose))))) */
});

View File

@@ -0,0 +1,7 @@
{
"name": "typed",
"dependencies": {
"mongodb": "*",
"mongoose": "*"
}
}

View File

@@ -0,0 +1,13 @@
declare module "mongodb" {
interface Collection {
find(query: any): any;
}
}
declare module "mongoose" {
interface Model {
find(query: any): any;
}
interface Query {
find(query: any): any;
}
}

View File

@@ -0,0 +1,6 @@
{
"include": ["."],
"compilerOptions": {
"esModuleInterop": true
}
}

View File

@@ -4,6 +4,8 @@
| mssql3.js:12:13:12:22 | 'password' | password |
| mysql1.js:6:14:6:17 | 'me' | user name |
| mysql1.js:7:14:7:21 | 'secret' | password |
| mysql1a.js:10:9:10:12 | 'me' | user name |
| mysql1a.js:11:13:11:20 | 'secret' | password |
| mysql2.js:7:21:7:25 | 'bob' | user name |
| mysql2.js:8:21:8:28 | 'secret' | password |
| mysql2tst.js:8:9:8:14 | 'root' | user name |

View File

@@ -5,6 +5,7 @@
| mssql2.js:22:24:22:43 | 'select 1 as number' |
| mysql1.js:13:18:13:43 | 'SELECT ... lution' |
| mysql1.js:18:18:22:1 | {\\n s ... vid']\\n} |
| mysql1a.js:17:18:17:43 | 'SELECT ... lution' |
| mysql2.js:12:12:12:37 | 'SELECT ... lution' |
| mysql2tst.js:14:3:14:62 | 'SELECT ... ` > 45' |
| mysql2tst.js:23:3:23:56 | 'SELECT ... e` > ?' |

View File

@@ -0,0 +1,24 @@
// Adapted from the documentation of https://github.com/mysqljs/mysql,
// which is licensed under the MIT license; see file mysqljs-License.
function importMySql() {
return require("mysql");
}
var connection = importMySql().createConnection({
host: 'localhost',
user: 'me',
password: 'secret',
database: 'my_db'
});
connection.connect();
connection.query('SELECT 1 + 1 AS solution', function (error, results, fields) {
if (error) throw error;
console.log('The solution is: ', results[0].solution);
});
connection.end();
exports.connection = connection;

View File

@@ -1,3 +1,4 @@
| exports.js:2:1:2:22 | exports ... = "yes" | This write to property 'answer' is useless, since $@ always overrides it. | exports.js:3:1:3:21 | exports ... = "no" | another property write |
| fieldInit.ts:10:3:10:8 | f = 4; | This write to property 'f' is useless, since $@ always overrides it. | fieldInit.ts:13:5:13:14 | this.f = 5 | another property write |
| fieldInit.ts:18:22:18:22 | h | This write to property 'h' is useless, since $@ always overrides it. | fieldInit.ts:19:5:19:14 | this.h = h | another property write |
| real-world-examples.js:5:4:5:11 | o.p = 42 | This write to property 'p' is useless, since $@ always overrides it. | real-world-examples.js:10:2:10:9 | o.p = 42 | another property write |

View File

@@ -0,0 +1,3 @@
var exports = module.exports;
exports.answer = "yes"; // NOT OK
exports.answer = "no";

View File

@@ -0,0 +1,5 @@
import rateLimit from 'express-rate-limit';
const rateLimitMiddleware = rateLimit();
export default rateLimitMiddleware;

View File

@@ -0,0 +1,8 @@
import express from 'express';
import rateLimiter from './rateLimit';
const app = express();
app.use(rateLimiter);
app.get('/', (req, res) => {
res.sendFile('index.html'); // OK
});