From f3e9104be4deed58ebee4c7746475dd783936b34 Mon Sep 17 00:00:00 2001 From: Max Schaefer Date: Fri, 14 Aug 2020 09:40:05 +0100 Subject: [PATCH 01/26] JavaScript: Add implementation of API graphs. --- javascript/ql/src/ApiGraphs.qll | 749 ++++++++++++++++++++++++++++++++ 1 file changed, 749 insertions(+) create mode 100644 javascript/ql/src/ApiGraphs.qll diff --git a/javascript/ql/src/ApiGraphs.qll b/javascript/ql/src/ApiGraphs.qll new file mode 100644 index 000000000000..d2e6873fc3ee --- /dev/null +++ b/javascript/ql/src/ApiGraphs.qll @@ -0,0 +1,749 @@ +/** + * Provides an implementation of _API graphs_, which are an abstract representation of the API + * surface of an NPM package. + * + * The nodes of the API graph are called _features_, with labeled edges representing how features + * relate to each other. For example, if one of the API features represents a function, then there + * will be features corresponding to the function's parameters, which are connected to the function + * feature by edges labeled `parameter `. There are special _points-to_ edges labeled with the + * empty string which express the fact that one feature is an alias for another. + */ + +import javascript +private import semmle.javascript.dataflow.internal.StepSummary + +/** + * Provides classes and predicates for working with APIs defined or used in a database. + */ +module API { + /** + * An abstract representation of an API feature such as a parameter of a function exported by the + * package. + */ + class Feature extends Impl::TFeature { + /** + * Gets a data-flow node corresponding to a use of this API feature. + */ + 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 this API + * feature. + */ + DataFlow::Node getADefinition() { Impl::def(this, result) } + + /** + * Gets a feature representing member `m` of this one. + */ + bindingset[m] + bindingset[result] + Feature getMember(string m) { result = getASuccessor(Label::member(m)) } + + /** + * Gets a feature representing a member with a computed name of this one. + */ + Feature getUnknownMember() { result = getASuccessor(Label::unknownMember()) } + + /** + * Gets a feature representing a member of this one. + */ + Feature getAMember() { + result = getASuccessor(Label::member(_)) or + result = getUnknownMember() + } + + /** + * Gets a feature representing an instance of this one. + */ + Feature getInstance() { result = getASuccessor(Label::instance()) } + + /** + * Gets a feature representing the `i`th parameter of this one. + */ + bindingset[i] + Feature getParameter(int i) { result = getASuccessor(Label::parameter(i)) } + + /** + * Gets the number of parameters of this feature. + */ + int getNumParameter() { + result = + max(string s | exists(getASuccessor(Label::parameterByStringIndex(s))) | s.toInt()) + 1 + } + + /** + * Gets a feature representing the last parameter of this one. + */ + Feature getLastParameter() { result = getParameter(getNumParameter() - 1) } + + /** + * Gets a feature representing the receiver of this one. + */ + Feature getReceiver() { result = getASuccessor(Label::receiver()) } + + /** + * Gets a feature representing a parameter or the receiver of this one. + */ + Feature getAParameter() { + result = getASuccessor(Label::parameterByStringIndex(_)) or + result = getReceiver() + } + + /** + * Gets a feature representing the result of this one. + */ + Feature getReturn() { result = getASuccessor(Label::return()) } + + /** + * Gets a feature representing the promised value wrapped in this promise. + */ + Feature getPromised() { result = getASuccessor(Label::promised()) } + + /** + * Gets a string representation of the lexicographically least among all shortest access paths + * from the root to this feature. + */ + string getPath() { result = min(string p | p = getAPath(Impl::distanceFromRoot(this)) | p) } + + /** + * Gets a feature such that there is an edge in the API graph between this feature and the other + * one, and that edge is labeled with `lbl`. This predicate skips through points-to edges. + */ + Feature getASuccessor(string lbl) { Impl::edge(this, lbl, result) } + + /** + * Gets a feature such that there is an edge in the API graph between that other feature and + * this one, and that edge is labeled with `lbl`. This predicate skips through points-to edges. + */ + Feature getAPredecessor(string lbl) { this = result.getASuccessor(lbl) } + + /** + * Gets a feature such that there is an edge in the API graph between this feature and the other + * one, possibly skipping through points-to edges. + */ + Feature getAPredecessor() { result = getAPredecessor(_) } + + /** + * Gets a feature such that there is an edge in the API graph between that other feature and + * this one, possibly skipping through points-to edges. + */ + Feature getASuccessor() { result = getASuccessor(_) } + + /** + * Holds if this feature may take its value from `that` feature. + * + * In other words, the value of a use of `that` may flow into the right-hand side of a + * definition of this feature. + */ + predicate refersTo(Feature that) { this.getADefinition() = that.getAUse() } + + /** + * Gets the unique data-flow that gives rise to this feature, if any. + */ + private DataFlow::Node getRepresentativeNode() { + this = Impl::MkClassInstance(result) or + this = Impl::MkUse(result) or + this = Impl::MkDef(result) or + this = Impl::MkAsyncFuncResult(result) + } + + /** + * Holds if this feature is located in file `path` between line `startline`, column `startcol`, + * and line `endline`, column `endcol`. + * + * For features 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) { + getRepresentativeNode().hasLocationInfo(path, startline, startcol, endline, endcol) + or + not exists(getRepresentativeNode()) and + path = "" and + startline = 0 and + startcol = 0 and + endline = 0 and + endcol = 0 + } + + /** + * Gets a textual representation of this feature. + */ + string toString() { + this = Impl::MkRoot() and result = "root" + or + exists(string m | + this = Impl::MkModule(m) and result = "module " + m + or + this = Impl::MkModuleImport(m) and result = "import " + m + or + this = Impl::MkModuleExport(m) and result = "export " + m + ) + or + exists(DataFlow::ClassNode cls | + this = Impl::MkClassInstance(cls) and result = "instance " + cls + ) + or + exists(DataFlow::FunctionNode fn | + this = Impl::MkAsyncFuncResult(fn) and result = "result " + fn + ) + or + exists(DataFlow::Node nd | + this = Impl::MkDef(nd) and result = "def " + getPath() + or + this = Impl::MkUse(nd) and result = "use " + getPath() + ) + or + exists(CanonicalName n | + this = Impl::MkCanonicalNameDef(n) and result = "def " + n + or + this = Impl::MkCanonicalNameUse(n) and result = "use " + n + ) + or + exists(AdditionalFeature a | this = Impl::MkAdditionalFeature(a) and result = a.getId()) + } + + /** + * Gets a path of the given `length` from the root to this feature. + */ + private string getAPath(int length) { + this instanceof Impl::MkRoot and + length = 0 and + result = "" + or + exists(Feature 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)] + } + } + + /** Gets the root feature. */ + Feature root() { result = Impl::MkRoot() } + + /** Gets a feature corresponding to an import of module `m`. */ + Feature moduleImport(string m) { + result = Impl::MkModuleImport(m) or + result = Impl::MkModuleImport(m).(Feature).getMember("default") + } + + /** Gets a feature corresponding to an export of module `m`. */ + Feature moduleExport(string m) { + result = Impl::MkModule(m).(Feature).getMember("exports") and + not result = moduleImport(_) + } + + /** Gets additional feature `a`. */ + Feature additionalNode(AdditionalFeature a) { result = Impl::MkAdditionalFeature(a) } + + /** + * 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 getADef(); + } + + /** + * A custom feature that is not captured by the standard implementation of API graphs. + */ + abstract class AdditionalFeature extends string { + bindingset[this] + AdditionalFeature() { any() } + + /** Gets a feature to which this feature has an edge labeled with `lbl` in the API graph. */ + abstract Feature getASuccessor(string lbl); + + /** Gets a feature which has an edge labeled with `lbl` to this feature in the API graph. */ + abstract Feature getAPredecessor(string lbl); + + /** Gets a unique string describing this feature. */ + abstract string getId(); + } + + /** + * Provides the actual implementation of API graphs, cached for performance. + * + * Ideally, we'd like features to correspond to (global) access paths, with edge labels + * corresponding to extending the access path by one element, or (in the case of points-to + * edges) recording alias information. We also want to be able to map features 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. However, expressing + * a condition like "this node hasn't been involved in constructing any predecessor of + * this feature in the API graph" without negative recursion is tricky. + * + * So instead most features are directly associated with a data-flow node, representing + * either a use or a definition of the feature. This ensures that we only have a finite + * number of features. However, we can now have multiple features with the same access + * path, which are essentially indistinguishable for a client of the API. + * + * On the other hand, a single feature 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 TFeature = + MkRoot() or + MkModule(string m) { exists(MkModuleExport(m)) or 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(nm.getModuleVariable().getAnAccess()) or + exists(nm.getExportsVariable().getAnAccess()) + ) + ) + } 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) { def(_, _, nd) } or + MkUse(DataFlow::SourceNode nd) { use(_, _, nd) } or + MkCanonicalNameDef(CanonicalName n) { isDefined(n) } or + MkCanonicalNameUse(CanonicalName n) { isUsed(n) } or + MkAdditionalFeature(AdditionalFeature a) + + private predicate hasSemantics(DataFlow::Node nd) { not nd.getTopLevel().isExterns() } + + /** Holds if `imp` is an import of module `m`. */ + private predicate imports(DataFlow::SourceNode 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()) or + exists(InvokeExpr invk | ast_node_symbol(invk, n)) + } + + private predicate isDefined(CanonicalName n) { + exists(ASTNode def | + def = n.(TypeName).getADefinition() or + def = n.(Namespace).getADefinition() or + def = n.(CanonicalFunctionName).getADefinition() + | + not def.isAmbient() + ) + } + + /** + * Holds if `rhs` is the right-hand side of a definition of a feature that should have an + * incoming edge from `base` labeled `lbl` in the API graph. + */ + cached + predicate def(Feature base, string lbl, DataFlow::Node rhs) { + hasSemantics(rhs) and + ( + base = MkRoot() and + rhs = lbl.(EntryPoint).getADef() + 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 | + def(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 + pred = PromiseTypeTracking::promiseStep(rhs, StoreStep(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 feature `nd`. + */ + cached + predicate def(Feature 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 feature that should have an incoming edge from `base` labeled + * `lbl` in the API graph. + */ + cached + predicate use(Feature base, string lbl, DataFlow::SourceNode 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) + | + 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 + ref = PromiseTypeTracking::promiseStep(pred, LoadStep(Promises::valueProp())) + ) + or + exists(DataFlow::Node def, DataFlow::FunctionNode fn | + def(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 | + def(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 feature `nd`. + */ + cached + predicate use(Feature nd, DataFlow::SourceNode ref) { + exists(string m, Module mod | nd = MkModule(m) and mod = importableModule(m) | + ref = mod.(NodeModule).getModuleVariable().getAnAccess().flow() + or + ref = DataFlow::parameterNode(mod.(AmdModule).getDefine().getModuleParameter()) + ) + or + exists(string m, Module mod | nd = MkModuleExport(m) and mod = importableModule(m) | + ref = mod.(NodeModule).getExportsVariable().getAnAccess().flow() + or + ref = DataFlow::parameterNode(mod.(AmdModule).getDefine().getExportsParameter()) + ) + 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 feature. + */ + 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 + def(_, 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 feature. + */ + 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(TFeature pred, string lbl, TFeature succ) { + exists(string m | + pred = MkRoot() and + lbl = Label::mod(m) and + succ = MkModule(m) + ) + or + exists(string m | + pred = MkModule(m) and + lbl = Label::member("exports") + | + succ = MkModuleExport(m) + or + succ = MkModuleImport(m) + ) + or + exists(DataFlow::SourceNode ref | + use(pred, lbl, ref) and + succ = MkUse(ref) + ) + or + exists(DataFlow::Node rhs | + def(pred, lbl, rhs) and + succ = MkDef(rhs) + ) + or + exists(DataFlow::Node def | + def(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) + ) + or + exists(AdditionalFeature a | + pred = MkAdditionalFeature(a) and + succ = a.getASuccessor(lbl) + or + pred = a.getAPredecessor(lbl) and + succ = MkAdditionalFeature(a) + ) + } + + /** + * Holds if there is an edge from `pred` to `succ` in the API graph. + */ + private predicate edge(TFeature pred, TFeature succ) { edge(pred, _, succ) } + + /** Gets the shortest distance from the root to `nd` in the API graph. */ + cached + int distanceFromRoot(TFeature nd) = shortestDistances(MkRoot/0, edge/2)(_, nd, result) + } +} + +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" } +} + +private class AdditionalSourceNode extends DataFlow::SourceNode::Range { + AdditionalSourceNode() { + exists(NodeModule m, RValue v | + v = m.getModuleVariable().getAnAccess() + or + v = m.getExportsVariable().getAnAccess() + | + this = v.flow() + ) + } +} From 68b3ccdc6546b38153805c08bf4e30f24af9bf8d Mon Sep 17 00:00:00 2001 From: Max Schaefer Date: Mon, 22 Jun 2020 14:14:57 +0100 Subject: [PATCH 02/26] JavaScript: Switch SQL modelling from source nodes to API graphs. --- .../src/semmle/javascript/frameworks/SQL.qll | 279 +++++++----------- 1 file changed, 101 insertions(+), 178 deletions(-) diff --git a/javascript/ql/src/semmle/javascript/frameworks/SQL.qll b/javascript/ql/src/semmle/javascript/frameworks/SQL.qll index fb12c5ab55df..ea6db61dc0ac 100644 --- a/javascript/ql/src/semmle/javascript/frameworks/SQL.qll +++ b/javascript/ql/src/semmle/javascript/frameworks/SQL.qll @@ -3,6 +3,7 @@ */ import javascript +private import ApiGraphs module SQL { /** A string-valued expression that is interpreted as a SQL command. */ @@ -28,42 +29,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"]) } + /** Gets the package name `mysql` or `mysql2`. */ + API::Feature mysql() { result = API::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 call to `mysql.createConnection`. */ + API::Feature createConnection() { result = mysql().getMember("createConnection").getReturn() } - /** Gets a reference to a MySQL pool. */ - private DataFlow::SourceNode pool() { result = pool(DataFlow::TypeTracker::end()) } + /** Gets a call to `mysql.createPool`. */ + API::Feature createPool() { result = mysql().getMember("createPool").getReturn() } - /** Gets a call to `mysql.createConnection`. */ - DataFlow::CallNode createConnection() { result = mysql().getAMemberCall("createConnection") } - - /** 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 data flow node that contains a freshly created MySQL connection instance. */ + API::Feature 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::Feature recv | recv = createPool() or recv = connection() | + this = recv.getMember("query").getReturn().getAUse() + ) + } override DataFlow::Node getAQueryArgument() { result = getArgument(0) } } @@ -76,7 +64,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 +80,9 @@ private module MySql { string kind; Credentials() { - exists(string prop | - this = [createConnection(), createPool()].getOptionArgument(0, prop).asExpr() and + exists(API::Feature call, string prop | + (call = createConnection() or call = createPool()) and + call.getAUse().asExpr().(CallExpr).hasOptionArgument(0, prop, this) and ( prop = "user" and kind = "user name" or @@ -105,49 +99,29 @@ private module MySql { * Provides classes modelling the `pg` package. */ private module Postgres { - /** Gets an expression that constructs a new connection pool. */ - DataFlow::InvokeNode newPool() { - // new require('pg').Pool() - result = DataFlow::moduleImport("pg").getAConstructorInvocation("Pool") - or - // new require('pg-pool') - result = DataFlow::moduleImport("pg-pool").getAnInstantiation() - } + /** Gets an expression of the form `new require('pg').Client()`. */ + API::Feature newClient() { result = API::moduleImport("pg").getMember("Client").getInstance() } - /** Gets a data flow node referring to a connection pool. */ - private DataFlow::SourceNode pool(DataFlow::TypeTracker t) { - t.start() and - result = newPool() + /** Gets a data flow node that holds a freshly created Postgres client instance. */ + API::Feature client() { + result = newClient() 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") + // pool.connect(function(err, client) { ... }) + result = newPool().getMember("connect").getParameter(0).getParameter(1) } - /** 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) - ) + /** Gets an expression that constructs a new connection pool. */ + API::Feature newPool() { + // new require('pg').Pool() + result = API::moduleImport("pg").getMember("Pool").getInstance() or - exists(DataFlow::TypeTracker t2 | result = client(t2).track(t2, t)) + // new require('pg-pool') + result = API::moduleImport("pg-pool").getInstance() } - /** 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 +136,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 +156,18 @@ private module Postgres { */ private module Sqlite { /** Gets a reference to the `sqlite3` module. */ - DataFlow::SourceNode sqlite() { - result = DataFlow::moduleImport("sqlite3") + API::Feature 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::Feature 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 +179,7 @@ private module Sqlite { meth = "prepare" or meth = "run" | - this = db().getAMethodCall(meth) + this = newDb().getMember(meth).getReturn().getAUse() ) } @@ -230,30 +197,24 @@ private module Sqlite { */ private module MsSql { /** Gets a reference to the `mssql` module. */ - DataFlow::SourceNode mssql() { result = DataFlow::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") - ) + API::Feature mssql() { result = API::moduleImport("mssql") } + + /** Gets an expression that creates a request object. */ + API::Feature 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::SourceNode).flowsToExpr(astNode.getTag()) + } override DataFlow::Node getAQueryArgument() { result = DataFlow::valueNode(astNode.getTemplate().getAnElement()) @@ -262,7 +223,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 +253,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 +274,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::Feature 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::Feature newSequelize() { result = sequelize().getInstance() } /** A call to `Sequelize.query`. */ - private class QueryCall extends DatabaseAccess, DataFlow::ValueNode { - override MethodCallExpr astNode; - - QueryCall() { this = sequelize().getAMethodCall("query") } + private class QueryCall extends DatabaseAccess, DataFlow::MethodCallNode { + QueryCall() { this = newSequelize().getMember("query").getReturn().getAUse() } - 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 +301,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 +328,36 @@ private module Spanner { /** * Gets a node that refers to the `Spanner` class */ - DataFlow::SourceNode spanner() { + API::Feature 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") - } - - /** 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)) + result = API::moduleImport("@google-cloud/spanner").getMember("Spanner") } - /** 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 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 an instance of the `Database` class. + */ + API::Feature database() { + result = + spanner().getReturn().getMember("instance").getReturn().getMember("database").getReturn() } - /** 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. + */ + API::Feature v1SpannerClient() { + result = spanner().getMember("v1").getMember("SpannerClient").getInstance() } - /** 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. + */ + API::Feature transaction() { + result = database().getMember("runTransaction").getParameter(0).getParameter(1) } - /** 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 +379,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 +388,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 +398,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() { From 6d68036d85dbc8ebec9171f7890ccf38f12d8ca9 Mon Sep 17 00:00:00 2001 From: Max Schaefer Date: Wed, 24 Jun 2020 09:56:01 +0100 Subject: [PATCH 03/26] JavaScript: Add test demonstrating more SQL flow. --- .../frameworks/SQL/Credentials.expected | 2 ++ .../frameworks/SQL/SqlString.expected | 1 + .../library-tests/frameworks/SQL/mysql1a.js | 24 +++++++++++++++++++ 3 files changed, 27 insertions(+) create mode 100644 javascript/ql/test/library-tests/frameworks/SQL/mysql1a.js diff --git a/javascript/ql/test/library-tests/frameworks/SQL/Credentials.expected b/javascript/ql/test/library-tests/frameworks/SQL/Credentials.expected index c6f466cdae91..a7d4af58a909 100644 --- a/javascript/ql/test/library-tests/frameworks/SQL/Credentials.expected +++ b/javascript/ql/test/library-tests/frameworks/SQL/Credentials.expected @@ -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 | diff --git a/javascript/ql/test/library-tests/frameworks/SQL/SqlString.expected b/javascript/ql/test/library-tests/frameworks/SQL/SqlString.expected index fb8a133937d6..b3e9e240944f 100644 --- a/javascript/ql/test/library-tests/frameworks/SQL/SqlString.expected +++ b/javascript/ql/test/library-tests/frameworks/SQL/SqlString.expected @@ -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` > ?' | diff --git a/javascript/ql/test/library-tests/frameworks/SQL/mysql1a.js b/javascript/ql/test/library-tests/frameworks/SQL/mysql1a.js new file mode 100644 index 000000000000..eff9c35101e8 --- /dev/null +++ b/javascript/ql/test/library-tests/frameworks/SQL/mysql1a.js @@ -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; From e34a821cc6a49c2bad98aebca03f66e99fe4f560 Mon Sep 17 00:00:00 2001 From: Max Schaefer Date: Mon, 22 Jun 2020 16:00:47 +0100 Subject: [PATCH 04/26] JavaScript: Switch system-command executor modelling from source nodes to API graphs. --- .../frameworks/SystemCommandExecutors.qll | 27 +++++++++++++------ 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/javascript/ql/src/semmle/javascript/frameworks/SystemCommandExecutors.qll b/javascript/ql/src/semmle/javascript/frameworks/SystemCommandExecutors.qll index 87c01f46c7b7..f149c02750f6 100644 --- a/javascript/ql/src/semmle/javascript/frameworks/SystemCommandExecutors.qll +++ b/javascript/ql/src/semmle/javascript/frameworks/SystemCommandExecutors.qll @@ -4,6 +4,7 @@ */ import javascript +private import ApiGraphs private class SystemCommandExecutors extends SystemCommandExecution, DataFlow::InvokeNode { int cmdArg; @@ -12,9 +13,13 @@ private class SystemCommandExecutors extends SystemCommandExecution, DataFlow::I boolean sync; SystemCommandExecutors() { - exists(string mod, DataFlow::SourceNode callee | + exists(string mod, API::Feature callee | exists(string method | - mod = "cross-spawn" and method = "sync" and cmdArg = 0 and shell = false and optionsArg = -1 + mod = "cross-spawn" and + method = "sync" and + cmdArg = 0 and + shell = false and + optionsArg = -1 or mod = "execa" and optionsArg = -1 and @@ -39,7 +44,7 @@ private class SystemCommandExecutors extends SystemCommandExecution, DataFlow::I optionsArg = 1 and shell = false | - callee = DataFlow::moduleMember(mod, method) and + callee = API::moduleImport(mod).getMember(method) and sync = getSync(method) ) or @@ -57,13 +62,19 @@ private class SystemCommandExecutors extends SystemCommandExecution, DataFlow::I ) or shell = true and - mod = "exec" and - optionsArg = -2 and - cmdArg = 0 + ( + mod = "exec" and + optionsArg = -2 and + cmdArg = 0 + or + mod = "remote-exec" and + cmdArg = 1 and + optionsArg = -1 + ) ) and - callee = DataFlow::moduleImport(mod) + callee = API::moduleImport(mod) | - this = callee.getACall() + this = callee.getReturn().getAUse() ) or this = DataFlow::moduleImport("foreground-child").getACall() and From e3a99060716716222becb374adfcb8d080170655 Mon Sep 17 00:00:00 2001 From: Max Schaefer Date: Tue, 25 Aug 2020 11:14:16 +0100 Subject: [PATCH 05/26] JavaScript: Switch `MissingRateLimiting.qll` to API graphs. The added test shows how this helps us avoid false positives. --- .../security/dataflow/MissingRateLimiting.qll | 25 ++++++++----------- .../query-tests/Security/CWE-770/rateLimit.ts | 5 ++++ .../test/query-tests/Security/CWE-770/tst2.ts | 8 ++++++ 3 files changed, 24 insertions(+), 14 deletions(-) create mode 100644 javascript/ql/test/query-tests/Security/CWE-770/rateLimit.ts create mode 100644 javascript/ql/test/query-tests/Security/CWE-770/tst2.ts diff --git a/javascript/ql/src/semmle/javascript/security/dataflow/MissingRateLimiting.qll b/javascript/ql/src/semmle/javascript/security/dataflow/MissingRateLimiting.qll index 82db9e0fad31..7af0537ffda2 100644 --- a/javascript/ql/src/semmle/javascript/security/dataflow/MissingRateLimiting.qll +++ b/javascript/ql/src/semmle/javascript/security/dataflow/MissingRateLimiting.qll @@ -24,6 +24,7 @@ import javascript private import semmle.javascript.frameworks.ConnectExpressShared::ConnectExpressShared +private import ApiGraphs // main concepts /** @@ -126,7 +127,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 +136,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,9 +145,9 @@ class BruteForceRateLimit extends RateLimiter { */ class RouteHandlerLimitedByExpressLimiter extends RateLimitedRouteHandlerExpr { RouteHandlerLimitedByExpressLimiter() { - exists(DataFlow::ModuleImportNode expressLimiter | - expressLimiter.getPath() = "express-limiter" and - expressLimiter.getACall().getArgument(0).getALocalSource().asExpr() = + exists(API::Feature expressLimiter | + expressLimiter = API::moduleImport("express-limiter") and + expressLimiter.getParameter(0).getADefinition().getALocalSource().asExpr() = this.getSetup().getRouter() ) } @@ -175,14 +172,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::Feature rateLimiterClass, API::Feature 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().getADefinition()) ) } } diff --git a/javascript/ql/test/query-tests/Security/CWE-770/rateLimit.ts b/javascript/ql/test/query-tests/Security/CWE-770/rateLimit.ts new file mode 100644 index 000000000000..e9f8854b8ccf --- /dev/null +++ b/javascript/ql/test/query-tests/Security/CWE-770/rateLimit.ts @@ -0,0 +1,5 @@ +import rateLimit from 'express-rate-limit'; + +const rateLimitMiddleware = rateLimit(); + +export default rateLimitMiddleware; diff --git a/javascript/ql/test/query-tests/Security/CWE-770/tst2.ts b/javascript/ql/test/query-tests/Security/CWE-770/tst2.ts new file mode 100644 index 000000000000..32e04e2ff206 --- /dev/null +++ b/javascript/ql/test/query-tests/Security/CWE-770/tst2.ts @@ -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 +}); From 500f7bd8faa8a46e8d0fcdc3f4efdb72f7464489 Mon Sep 17 00:00:00 2001 From: Max Schaefer Date: Wed, 26 Aug 2020 15:25:31 +0100 Subject: [PATCH 06/26] JavaScript: Reduce complexity of `SystemCommandExecutors` charpred. --- .../frameworks/SystemCommandExecutors.qll | 130 +++++++++--------- 1 file changed, 65 insertions(+), 65 deletions(-) diff --git a/javascript/ql/src/semmle/javascript/frameworks/SystemCommandExecutors.qll b/javascript/ql/src/semmle/javascript/frameworks/SystemCommandExecutors.qll index f149c02750f6..1985ab6aba53 100644 --- a/javascript/ql/src/semmle/javascript/frameworks/SystemCommandExecutors.qll +++ b/javascript/ql/src/semmle/javascript/frameworks/SystemCommandExecutors.qll @@ -6,6 +6,56 @@ import javascript private import ApiGraphs +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 + or + mod = "remote-exec" and + cmdArg = 1 and + optionsArg = -1 + ) +} + 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). @@ -14,70 +64,20 @@ private class SystemCommandExecutors extends SystemCommandExecution, DataFlow::I SystemCommandExecutors() { exists(string mod, API::Feature 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 = API::moduleImport(mod).getMember(method) and - sync = getSync(method) + exists(string fn | + execApi(mod, fn, cmdArg, optionsArg, shell) and + sync = getSync(fn) and + callee = API::moduleImport(mod).getMember(fn) ) 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 - or - mod = "remote-exec" and - cmdArg = 1 and - optionsArg = -1 - ) - ) and callee = API::moduleImport(mod) | this = callee.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 @@ -121,19 +121,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::Feature ssh2, API::Feature 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::Feature ssh2stream | + ssh2stream = API::moduleImport("ssh2-streams").getMember("SSH2Stream") and + this = ssh2stream.getInstance().getMember("exec").getReturn().getAUse() and cmdArg = 1 ) } @@ -148,7 +148,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") } From 82d92dc726bbd6fe083db957312f66c9ada2e57b Mon Sep 17 00:00:00 2001 From: Max Schaefer Date: Wed, 26 Aug 2020 16:01:34 +0100 Subject: [PATCH 07/26] JavaScript: Avoid bad join order. The optimiser decided that it would be a great idea to start the pipeline with `getReturn().getAUse().(DataFlow::InvokeNode)`. It's not. --- .../javascript/frameworks/SystemCommandExecutors.qll | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/javascript/ql/src/semmle/javascript/frameworks/SystemCommandExecutors.qll b/javascript/ql/src/semmle/javascript/frameworks/SystemCommandExecutors.qll index 1985ab6aba53..c02c0d7f566f 100644 --- a/javascript/ql/src/semmle/javascript/frameworks/SystemCommandExecutors.qll +++ b/javascript/ql/src/semmle/javascript/frameworks/SystemCommandExecutors.qll @@ -63,18 +63,16 @@ private class SystemCommandExecutors extends SystemCommandExecution, DataFlow::I boolean sync; SystemCommandExecutors() { - exists(string mod, API::Feature callee | + exists(string mod | exists(string fn | execApi(mod, fn, cmdArg, optionsArg, shell) and sync = getSync(fn) and - callee = API::moduleImport(mod).getMember(fn) + this = API::moduleImport(mod).getMember(fn).getReturn().getAUse() ) or execApi(mod, cmdArg, optionsArg, shell) and sync = false and - callee = API::moduleImport(mod) - | - this = callee.getReturn().getAUse() + this = API::moduleImport(mod).getReturn().getAUse() ) or this = API::moduleImport("foreground-child").getReturn().getAUse() and From df498181526f21c665f38fa105db6f849e6cfe1d Mon Sep 17 00:00:00 2001 From: Max Schaefer Date: Wed, 2 Sep 2020 17:31:35 +0100 Subject: [PATCH 08/26] JavaScript: Address review comments. --- javascript/ql/src/ApiGraphs.qll | 45 +++++++++++++------ .../src/semmle/javascript/frameworks/SQL.qll | 2 +- .../frameworks/SystemCommandExecutors.qll | 12 ++--- .../security/dataflow/MissingRateLimiting.qll | 9 ++-- 4 files changed, 38 insertions(+), 30 deletions(-) diff --git a/javascript/ql/src/ApiGraphs.qll b/javascript/ql/src/ApiGraphs.qll index d2e6873fc3ee..183645d89573 100644 --- a/javascript/ql/src/ApiGraphs.qll +++ b/javascript/ql/src/ApiGraphs.qll @@ -17,12 +17,18 @@ private import semmle.javascript.dataflow.internal.StepSummary */ module API { /** - * An abstract representation of an API feature such as a parameter of a function exported by the - * package. + * An abstract representation of an API feature such as a function exported by an npm package, + * a parameter of such a function, or its result. */ class Feature extends Impl::TFeature { /** * Gets a data-flow node corresponding to a use of this API feature. + * + * For example, `require('fs').readFileSync` is a use of the feature `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 feature corresponding to the first parameter of `plusOne`. */ DataFlow::Node getAUse() { exists(DataFlow::SourceNode src | Impl::use(this, src) | @@ -33,8 +39,18 @@ module API { /** * Gets a data-flow node corresponding to the right-hand side of a definition of this API * feature. + * + * 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 feature `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 getADefinition() { Impl::def(this, result) } + DataFlow::Node getARhs() { Impl::rhs(this, result) } /** * Gets a feature representing member `m` of this one. @@ -57,7 +73,8 @@ module API { } /** - * Gets a feature representing an instance of this one. + * Gets a feature representing an instance of this one, that is, an object whose + * constructor is this feature. */ Feature getInstance() { result = getASuccessor(Label::instance()) } @@ -139,7 +156,7 @@ module API { * In other words, the value of a use of `that` may flow into the right-hand side of a * definition of this feature. */ - predicate refersTo(Feature that) { this.getADefinition() = that.getAUse() } + predicate refersTo(Feature that) { this.getARhs() = that.getAUse() } /** * Gets the unique data-flow that gives rise to this feature, if any. @@ -327,7 +344,7 @@ module API { MkAsyncFuncResult(DataFlow::FunctionNode f) { f = trackDefNode(_) and f.getFunction().isAsync() and hasSemantics(f) } or - MkDef(DataFlow::Node nd) { def(_, _, nd) } or + MkDef(DataFlow::Node nd) { rhs(_, _, nd) } or MkUse(DataFlow::SourceNode nd) { use(_, _, nd) } or MkCanonicalNameDef(CanonicalName n) { isDefined(n) } or MkCanonicalNameUse(CanonicalName n) { isUsed(n) } or @@ -375,7 +392,7 @@ module API { * incoming edge from `base` labeled `lbl` in the API graph. */ cached - predicate def(Feature base, string lbl, DataFlow::Node rhs) { + predicate rhs(Feature base, string lbl, DataFlow::Node rhs) { hasSemantics(rhs) and ( base = MkRoot() and @@ -388,7 +405,7 @@ module API { ) or exists(DataFlow::Node def, DataFlow::SourceNode pred | - def(base, def) and pred = trackDefNode(def) + rhs(base, def) and pred = trackDefNode(def) | exists(DataFlow::PropWrite pw | pw = pred.getAPropertyWrite() | lbl = Label::memberFromRef(pw) and @@ -441,7 +458,7 @@ module API { * Holds if `rhs` is the right-hand side of a definition of feature `nd`. */ cached - predicate def(Feature nd, DataFlow::Node rhs) { + predicate rhs(Feature nd, DataFlow::Node rhs) { exists(string m | nd = MkModuleExport(m) | exports(m, rhs)) or nd = MkDef(rhs) @@ -480,7 +497,7 @@ module API { ) or exists(DataFlow::Node def, DataFlow::FunctionNode fn | - def(base, def) and fn = trackDefNode(def) + rhs(base, def) and fn = trackDefNode(def) | exists(int i | lbl = Label::parameter(i) and @@ -492,7 +509,7 @@ module API { ) or exists(DataFlow::Node def, DataFlow::ClassNode cls, int i | - def(base, def) and cls = trackDefNode(def) + rhs(base, def) and cls = trackDefNode(def) | lbl = Label::parameter(i) and ref = cls.getConstructor().getParameter(i) @@ -580,7 +597,7 @@ module API { private DataFlow::SourceNode trackDefNode(DataFlow::Node nd, DataFlow::TypeBackTracker t) { t.start() and - def(_, nd) and + rhs(_, nd) and result = nd.getALocalSource() or exists(DataFlow::TypeBackTracker t2 | result = trackDefNode(nd, t2).backtrack(t2, t)) @@ -627,12 +644,12 @@ module API { ) or exists(DataFlow::Node rhs | - def(pred, lbl, rhs) and + rhs(pred, lbl, rhs) and succ = MkDef(rhs) ) or exists(DataFlow::Node def | - def(pred, def) and + rhs(pred, def) and lbl = Label::instance() and succ = MkClassInstance(trackDefNode(def)) ) diff --git a/javascript/ql/src/semmle/javascript/frameworks/SQL.qll b/javascript/ql/src/semmle/javascript/frameworks/SQL.qll index ea6db61dc0ac..f18122a1f786 100644 --- a/javascript/ql/src/semmle/javascript/frameworks/SQL.qll +++ b/javascript/ql/src/semmle/javascript/frameworks/SQL.qll @@ -81,7 +81,7 @@ private module MySql { Credentials() { exists(API::Feature call, string prop | - (call = createConnection() or call = createPool()) and + call in [createConnection(), createPool()] and call.getAUse().asExpr().(CallExpr).hasOptionArgument(0, prop, this) and ( prop = "user" and kind = "user name" diff --git a/javascript/ql/src/semmle/javascript/frameworks/SystemCommandExecutors.qll b/javascript/ql/src/semmle/javascript/frameworks/SystemCommandExecutors.qll index c02c0d7f566f..8e2df4011a08 100644 --- a/javascript/ql/src/semmle/javascript/frameworks/SystemCommandExecutors.qll +++ b/javascript/ql/src/semmle/javascript/frameworks/SystemCommandExecutors.qll @@ -45,15 +45,9 @@ private predicate execApi(string mod, int cmdArg, int optionsArg, boolean shell) ) or shell = true and - ( - mod = "exec" and - optionsArg = -2 and - cmdArg = 0 - or - mod = "remote-exec" and - cmdArg = 1 and - optionsArg = -1 - ) + mod = "exec" and + optionsArg = -2 and + cmdArg = 0 } private class SystemCommandExecutors extends SystemCommandExecution, DataFlow::InvokeNode { diff --git a/javascript/ql/src/semmle/javascript/security/dataflow/MissingRateLimiting.qll b/javascript/ql/src/semmle/javascript/security/dataflow/MissingRateLimiting.qll index 7af0537ffda2..85638acd10f7 100644 --- a/javascript/ql/src/semmle/javascript/security/dataflow/MissingRateLimiting.qll +++ b/javascript/ql/src/semmle/javascript/security/dataflow/MissingRateLimiting.qll @@ -145,11 +145,8 @@ class BruteForceRateLimit extends RateLimiter { */ class RouteHandlerLimitedByExpressLimiter extends RateLimitedRouteHandlerExpr { RouteHandlerLimitedByExpressLimiter() { - exists(API::Feature expressLimiter | - expressLimiter = API::moduleImport("express-limiter") and - expressLimiter.getParameter(0).getADefinition().getALocalSource().asExpr() = - this.getSetup().getRouter() - ) + API::moduleImport("express-limiter").getParameter(0).getARhs().getALocalSource().asExpr() = + this.getSetup().getRouter() } } @@ -179,7 +176,7 @@ class RateLimiterFlexibleRateLimiter extends DataFlow::FunctionNode { rateLimiterClass = API::moduleImport("rate-limiter-flexible").getMember(rateLimiterClassName) and rateLimiterConsume = rateLimiterClass.getInstance().getMember("consume") and request.getParameter() = getRouteHandlerParameter(getFunction(), "request") and - request.getAPropertyRead().flowsTo(rateLimiterConsume.getAParameter().getADefinition()) + request.getAPropertyRead().flowsTo(rateLimiterConsume.getAParameter().getARhs()) ) } } From d81d80430eff7f7ac67923b6760aaa9935ac0735 Mon Sep 17 00:00:00 2001 From: Max Schaefer Date: Wed, 2 Sep 2020 17:35:38 +0100 Subject: [PATCH 09/26] JavaScript: Add a regression test for `DeadStoreOfProperty`. --- .../DeadStoreOfProperty/DeadStoreOfProperty.expected | 1 + .../query-tests/Declarations/DeadStoreOfProperty/exports.js | 3 +++ 2 files changed, 4 insertions(+) create mode 100644 javascript/ql/test/query-tests/Declarations/DeadStoreOfProperty/exports.js diff --git a/javascript/ql/test/query-tests/Declarations/DeadStoreOfProperty/DeadStoreOfProperty.expected b/javascript/ql/test/query-tests/Declarations/DeadStoreOfProperty/DeadStoreOfProperty.expected index 68890cc34da7..b22ab1e4b570 100644 --- a/javascript/ql/test/query-tests/Declarations/DeadStoreOfProperty/DeadStoreOfProperty.expected +++ b/javascript/ql/test/query-tests/Declarations/DeadStoreOfProperty/DeadStoreOfProperty.expected @@ -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 | | 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 | | real-world-examples.js:15:9:15:18 | o.p1 += 42 | This write to property 'p1' is useless, since $@ always overrides it. | real-world-examples.js:15:2:15:18 | o.p1 = o.p1 += 42 | another property write | diff --git a/javascript/ql/test/query-tests/Declarations/DeadStoreOfProperty/exports.js b/javascript/ql/test/query-tests/Declarations/DeadStoreOfProperty/exports.js new file mode 100644 index 000000000000..c4b70604781d --- /dev/null +++ b/javascript/ql/test/query-tests/Declarations/DeadStoreOfProperty/exports.js @@ -0,0 +1,3 @@ +var exports = module.exports; +exports.answer = "yes"; // NOT OK +exports.answer = "no"; From 9840a7ddfb4302ca0b132be60055ac0bc1c3697d Mon Sep 17 00:00:00 2001 From: Max Schaefer Date: Wed, 2 Sep 2020 14:34:52 +0100 Subject: [PATCH 10/26] JavaScript: Add utility predicate `SSA::implicitInit`. --- javascript/ql/src/semmle/javascript/SSA.qll | 3 +++ 1 file changed, 3 insertions(+) diff --git a/javascript/ql/src/semmle/javascript/SSA.qll b/javascript/ql/src/semmle/javascript/SSA.qll index a3d74e4f7246..ac574e4ab10b 100644 --- a/javascript/ql/src/semmle/javascript/SSA.qll +++ b/javascript/ql/src/semmle/javascript/SSA.qll @@ -737,6 +737,9 @@ class SsaRefinementNode extends SsaPseudoDefinition, TRefinement { } module SSA { + /** Gets the SSA definition corresponding to the implicit initialization of `v`. */ + SsaImplicitInit implicitInit(SsaSourceVariable v) { result.getSourceVariable() = v } + /** Gets the SSA definition corresponding to `d`. */ SsaExplicitDefinition definition(VarDef d) { result.getDef() = d } From 702192c316ced35725dd187dbc682d2bc3c365e0 Mon Sep 17 00:00:00 2001 From: Max Schaefer Date: Wed, 2 Sep 2020 19:52:13 +0100 Subject: [PATCH 11/26] JavaScript: Make implicit inits of `module` and `exports` source nodes. This is instead of making every access to those variables source nodes, and fixes a regression in `DeadStoreOfProperty`. --- javascript/ql/src/ApiGraphs.qll | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/javascript/ql/src/ApiGraphs.qll b/javascript/ql/src/ApiGraphs.qll index 183645d89573..053879ca009b 100644 --- a/javascript/ql/src/ApiGraphs.qll +++ b/javascript/ql/src/ApiGraphs.qll @@ -334,8 +334,7 @@ module API { exports(m, _, _) or exists(NodeModule nm | nm = mod | - exists(nm.getModuleVariable().getAnAccess()) or - exists(nm.getExportsVariable().getAnAccess()) + exists(SSA::implicitInit([nm.getModuleVariable(), nm.getExportsVariable()])) ) ) } or @@ -529,13 +528,13 @@ module API { cached predicate use(Feature nd, DataFlow::SourceNode ref) { exists(string m, Module mod | nd = MkModule(m) and mod = importableModule(m) | - ref = mod.(NodeModule).getModuleVariable().getAnAccess().flow() + 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 = mod.(NodeModule).getExportsVariable().getAnAccess().flow() + ref = DataFlow::ssaDefinitionNode(SSA::implicitInit(mod.(NodeModule).getExportsVariable())) or ref = DataFlow::parameterNode(mod.(AmdModule).getDefine().getExportsParameter()) ) @@ -753,14 +752,14 @@ private module Label { 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, RValue v | - v = m.getModuleVariable().getAnAccess() - or - v = m.getExportsVariable().getAnAccess() - | - this = v.flow() + exists(NodeModule m, Variable v | + v in [m.getModuleVariable(), m.getExportsVariable()] and + this = DataFlow::ssaDefinitionNode(SSA::implicitInit(v)) ) } } From ec3c1f114c6e8b222349ff54ce4edd7865803198 Mon Sep 17 00:00:00 2001 From: Max Schaefer Date: Wed, 2 Sep 2020 21:40:34 +0100 Subject: [PATCH 12/26] JavaScript: Simplify steps through promises. --- javascript/ql/src/ApiGraphs.qll | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/javascript/ql/src/ApiGraphs.qll b/javascript/ql/src/ApiGraphs.qll index 053879ca009b..7532b4cd7d3f 100644 --- a/javascript/ql/src/ApiGraphs.qll +++ b/javascript/ql/src/ApiGraphs.qll @@ -10,7 +10,6 @@ */ import javascript -private import semmle.javascript.dataflow.internal.StepSummary /** * Provides classes and predicates for working with APIs defined or used in a database. @@ -418,7 +417,7 @@ module API { ) or lbl = Label::promised() and - pred = PromiseTypeTracking::promiseStep(rhs, StoreStep(Promises::valueProp())) + PromiseFlow::storeStep(rhs, pred, Promises::valueProp()) ) or exists(DataFlow::ClassNode cls, string name | @@ -492,7 +491,7 @@ module API { ref = pred.getAnInvocation() or lbl = Label::promised() and - ref = PromiseTypeTracking::promiseStep(pred, LoadStep(Promises::valueProp())) + PromiseFlow::loadStep(pred, ref, Promises::valueProp()) ) or exists(DataFlow::Node def, DataFlow::FunctionNode fn | From 924ef6ae5d6c86118985c83c9a1bce472c62bcbf Mon Sep 17 00:00:00 2001 From: Max Schaefer <54907921+max-schaefer@users.noreply.github.com> Date: Thu, 3 Sep 2020 14:04:23 +0100 Subject: [PATCH 13/26] Apply suggestions from code review Co-authored-by: Erik Krogh Kristensen --- javascript/ql/src/ApiGraphs.qll | 2 +- javascript/ql/src/semmle/javascript/frameworks/SQL.qll | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/javascript/ql/src/ApiGraphs.qll b/javascript/ql/src/ApiGraphs.qll index 7532b4cd7d3f..e838b282572b 100644 --- a/javascript/ql/src/ApiGraphs.qll +++ b/javascript/ql/src/ApiGraphs.qll @@ -372,7 +372,7 @@ module API { private predicate isUsed(CanonicalName n) { exists(n.(TypeName).getAnAccess()) or exists(n.(Namespace).getAnAccess()) or - exists(InvokeExpr invk | ast_node_symbol(invk, n)) + exists(InvokeExpr invk | invk.getResolvedCalleeName() = n) } private predicate isDefined(CanonicalName n) { diff --git a/javascript/ql/src/semmle/javascript/frameworks/SQL.qll b/javascript/ql/src/semmle/javascript/frameworks/SQL.qll index f18122a1f786..2ee7d6a22852 100644 --- a/javascript/ql/src/semmle/javascript/frameworks/SQL.qll +++ b/javascript/ql/src/semmle/javascript/frameworks/SQL.qll @@ -213,7 +213,7 @@ private module MsSql { override TaggedTemplateExpr astNode; QueryTemplateExpr() { - mssql().getMember("query").getAUse().(DataFlow::SourceNode).flowsToExpr(astNode.getTag()) + mssql().getMember("query").getAUse() = DataFlow::valueNode(astNode.getTag()) } override DataFlow::Node getAQueryArgument() { From e77948103f8a132c71d5909b2d340b384218b52e Mon Sep 17 00:00:00 2001 From: Max Schaefer Date: Thu, 3 Sep 2020 08:22:21 +0100 Subject: [PATCH 14/26] JavaScript: Remove `AdditionalFeature` from `ApiGraphs`. I ended up not using it for flow summaries, so at this point it is purely speculative generality. We can reintroduce it later if we need to. --- javascript/ql/src/ApiGraphs.qll | 33 +-------------------------------- 1 file changed, 1 insertion(+), 32 deletions(-) diff --git a/javascript/ql/src/ApiGraphs.qll b/javascript/ql/src/ApiGraphs.qll index e838b282572b..1b98239e3bee 100644 --- a/javascript/ql/src/ApiGraphs.qll +++ b/javascript/ql/src/ApiGraphs.qll @@ -218,8 +218,6 @@ module API { or this = Impl::MkCanonicalNameUse(n) and result = "use " + n ) - or - exists(AdditionalFeature a | this = Impl::MkAdditionalFeature(a) and result = a.getId()) } /** @@ -259,9 +257,6 @@ module API { not result = moduleImport(_) } - /** Gets additional feature `a`. */ - Feature additionalNode(AdditionalFeature a) { result = Impl::MkAdditionalFeature(a) } - /** * An API entry point. * @@ -279,23 +274,6 @@ module API { abstract DataFlow::Node getADef(); } - /** - * A custom feature that is not captured by the standard implementation of API graphs. - */ - abstract class AdditionalFeature extends string { - bindingset[this] - AdditionalFeature() { any() } - - /** Gets a feature to which this feature has an edge labeled with `lbl` in the API graph. */ - abstract Feature getASuccessor(string lbl); - - /** Gets a feature which has an edge labeled with `lbl` to this feature in the API graph. */ - abstract Feature getAPredecessor(string lbl); - - /** Gets a unique string describing this feature. */ - abstract string getId(); - } - /** * Provides the actual implementation of API graphs, cached for performance. * @@ -345,8 +323,7 @@ module API { MkDef(DataFlow::Node nd) { rhs(_, _, nd) } or MkUse(DataFlow::SourceNode nd) { use(_, _, nd) } or MkCanonicalNameDef(CanonicalName n) { isDefined(n) } or - MkCanonicalNameUse(CanonicalName n) { isUsed(n) } or - MkAdditionalFeature(AdditionalFeature a) + MkCanonicalNameUse(CanonicalName n) { isUsed(n) } private predicate hasSemantics(DataFlow::Node nd) { not nd.getTopLevel().isExterns() } @@ -674,14 +651,6 @@ module API { lbl = Label::return() and succ = MkAsyncFuncResult(f) ) - or - exists(AdditionalFeature a | - pred = MkAdditionalFeature(a) and - succ = a.getASuccessor(lbl) - or - pred = a.getAPredecessor(lbl) and - succ = MkAdditionalFeature(a) - ) } /** From d8fbf60cbf877bf819cf22e2a5a837acd55a1e69 Mon Sep 17 00:00:00 2001 From: Max Schaefer Date: Thu, 3 Sep 2020 08:26:14 +0100 Subject: [PATCH 15/26] JavaScript: Weaken a few types to stay under BDD node limit. `SourceNode` in cached layers seems particularly problematic. --- javascript/ql/src/ApiGraphs.qll | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/javascript/ql/src/ApiGraphs.qll b/javascript/ql/src/ApiGraphs.qll index 1b98239e3bee..226ced55d0aa 100644 --- a/javascript/ql/src/ApiGraphs.qll +++ b/javascript/ql/src/ApiGraphs.qll @@ -321,14 +321,14 @@ module API { f = trackDefNode(_) and f.getFunction().isAsync() and hasSemantics(f) } or MkDef(DataFlow::Node nd) { rhs(_, _, nd) } or - MkUse(DataFlow::SourceNode nd) { use(_, _, nd) } or + MkUse(DataFlow::Node nd) { use(_, _, nd) } or MkCanonicalNameDef(CanonicalName n) { isDefined(n) } or MkCanonicalNameUse(CanonicalName n) { isUsed(n) } private predicate hasSemantics(DataFlow::Node nd) { not nd.getTopLevel().isExterns() } /** Holds if `imp` is an import of module `m`. */ - private predicate imports(DataFlow::SourceNode imp, string 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 @@ -367,7 +367,7 @@ module API { * incoming edge from `base` labeled `lbl` in the API graph. */ cached - predicate rhs(Feature base, string lbl, DataFlow::Node rhs) { + predicate rhs(TFeature base, string lbl, DataFlow::Node rhs) { hasSemantics(rhs) and ( base = MkRoot() and @@ -433,7 +433,7 @@ module API { * Holds if `rhs` is the right-hand side of a definition of feature `nd`. */ cached - predicate rhs(Feature nd, DataFlow::Node rhs) { + predicate rhs(TFeature nd, DataFlow::Node rhs) { exists(string m | nd = MkModuleExport(m) | exports(m, rhs)) or nd = MkDef(rhs) @@ -449,7 +449,7 @@ module API { * `lbl` in the API graph. */ cached - predicate use(Feature base, string lbl, DataFlow::SourceNode ref) { + predicate use(TFeature base, string lbl, DataFlow::Node ref) { hasSemantics(ref) and ( base = MkRoot() and @@ -502,7 +502,7 @@ module API { * Holds if `ref` is a use of feature `nd`. */ cached - predicate use(Feature nd, DataFlow::SourceNode ref) { + predicate use(TFeature nd, DataFlow::Node ref) { exists(string m, Module mod | nd = MkModule(m) and mod = importableModule(m) | ref = DataFlow::ssaDefinitionNode(SSA::implicitInit(mod.(NodeModule).getModuleVariable())) or From 7239f1fb6fec6fe6474e3c144b901f72cc628ce8 Mon Sep 17 00:00:00 2001 From: Max Schaefer Date: Thu, 3 Sep 2020 08:27:36 +0100 Subject: [PATCH 16/26] JavaScript: Distinguish more carefully between def and use nodes in API graphs. In particular, we now have two different kinds of module features: module definitions and module uses. For the most part, `API::Definition`s correspond to right-hand sides in the data-flow graph, and `API::Use`s correspond to references. However, module definitions can have references (via the CommonJS `module` variable), and so can their exports (via `module.exports` or `exports`). Note that this is different from references to uses of the module, which are simply imports. --- javascript/ql/src/ApiGraphs.qll | 85 +++++++++++++++++---------------- 1 file changed, 44 insertions(+), 41 deletions(-) diff --git a/javascript/ql/src/ApiGraphs.qll b/javascript/ql/src/ApiGraphs.qll index 226ced55d0aa..59c74abba761 100644 --- a/javascript/ql/src/ApiGraphs.qll +++ b/javascript/ql/src/ApiGraphs.qll @@ -189,35 +189,7 @@ module API { * Gets a textual representation of this feature. */ string toString() { - this = Impl::MkRoot() and result = "root" - or - exists(string m | - this = Impl::MkModule(m) and result = "module " + m - or - this = Impl::MkModuleImport(m) and result = "import " + m - or - this = Impl::MkModuleExport(m) and result = "export " + m - ) - or - exists(DataFlow::ClassNode cls | - this = Impl::MkClassInstance(cls) and result = "instance " + cls - ) - or - exists(DataFlow::FunctionNode fn | - this = Impl::MkAsyncFuncResult(fn) and result = "result " + fn - ) - or - exists(DataFlow::Node nd | - this = Impl::MkDef(nd) and result = "def " + getPath() - or - this = Impl::MkUse(nd) and result = "use " + getPath() - ) - or - exists(CanonicalName n | - this = Impl::MkCanonicalNameDef(n) and result = "def " + n - or - this = Impl::MkCanonicalNameUse(n) and result = "use " + n - ) + none() // defined in subclasses } /** @@ -242,8 +214,23 @@ module API { } } + /** The root feature of an API graph. */ + class Root extends Feature, Impl::MkRoot { + override string toString() { result = "root" } + } + + /** A feature corresponding to a definition of an API component. */ + class Definition extends Feature, Impl::TDef { + override string toString() { result = "def " + getPath() } + } + + /** A feature corresponding to the use of an API component. */ + class Use extends Feature, Impl::TUse { + override string toString() { result = "use " + getPath() } + } + /** Gets the root feature. */ - Feature root() { result = Impl::MkRoot() } + Root root() { any() } /** Gets a feature corresponding to an import of module `m`. */ Feature moduleImport(string m) { @@ -252,10 +239,7 @@ module API { } /** Gets a feature corresponding to an export of module `m`. */ - Feature moduleExport(string m) { - result = Impl::MkModule(m).(Feature).getMember("exports") and - not result = moduleImport(_) - } + Feature moduleExport(string m) { result = Impl::MkModuleDef(m).(Feature).getMember("exports") } /** * An API entry point. @@ -302,7 +286,8 @@ module API { cached newtype TFeature = MkRoot() or - MkModule(string m) { exists(MkModuleExport(m)) or exists(MkModuleImport(m)) } 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 @@ -325,6 +310,13 @@ module API { 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`. */ @@ -458,6 +450,9 @@ module API { 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 @@ -503,7 +498,7 @@ module API { */ cached predicate use(TFeature nd, DataFlow::Node ref) { - exists(string m, Module mod | nd = MkModule(m) and mod = importableModule(m) | + 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()) @@ -513,6 +508,10 @@ module API { 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 | @@ -600,16 +599,20 @@ module API { predicate edge(TFeature pred, string lbl, TFeature succ) { exists(string m | pred = MkRoot() and - lbl = Label::mod(m) and - succ = MkModule(m) + lbl = Label::mod(m) + | + succ = MkModuleDef(m) + or + succ = MkModuleUse(m) ) or exists(string m | - pred = MkModule(m) and - lbl = Label::member("exports") - | + 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 From aaa70e4ad37868f5286f63ce9a667f57620e201c Mon Sep 17 00:00:00 2001 From: Max Schaefer Date: Thu, 3 Sep 2020 08:32:00 +0100 Subject: [PATCH 17/26] JavaScript: Make API-graph edge labels accessible outside `ApiGraphs.qll`. --- javascript/ql/src/ApiGraphs.qll | 2 ++ 1 file changed, 2 insertions(+) diff --git a/javascript/ql/src/ApiGraphs.qll b/javascript/ql/src/ApiGraphs.qll index 59c74abba761..17dbe4fdc2b8 100644 --- a/javascript/ql/src/ApiGraphs.qll +++ b/javascript/ql/src/ApiGraphs.qll @@ -665,6 +665,8 @@ module API { cached int distanceFromRoot(TFeature nd) = shortestDistances(MkRoot/0, edge/2)(_, nd, result) } + + import Label as EdgeLabel } private module Label { From 985399f4cf16e83f4d54aba12df622158e9bdfad Mon Sep 17 00:00:00 2001 From: Max Schaefer Date: Thu, 3 Sep 2020 08:36:28 +0100 Subject: [PATCH 18/26] JavaScript: Move `ApiGraphs` library to `semmle.javascript` and import it from `javascript.qll`. --- javascript/ql/src/javascript.qll | 1 + javascript/ql/src/{ => semmle/javascript}/ApiGraphs.qll | 0 javascript/ql/src/semmle/javascript/frameworks/SQL.qll | 1 - .../src/semmle/javascript/frameworks/SystemCommandExecutors.qll | 1 - .../semmle/javascript/security/dataflow/MissingRateLimiting.qll | 1 - 5 files changed, 1 insertion(+), 3 deletions(-) rename javascript/ql/src/{ => semmle/javascript}/ApiGraphs.qll (100%) diff --git a/javascript/ql/src/javascript.qll b/javascript/ql/src/javascript.qll index 877b47651ef7..4e223ccc3ccc 100644 --- a/javascript/ql/src/javascript.qll +++ b/javascript/ql/src/javascript.qll @@ -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 diff --git a/javascript/ql/src/ApiGraphs.qll b/javascript/ql/src/semmle/javascript/ApiGraphs.qll similarity index 100% rename from javascript/ql/src/ApiGraphs.qll rename to javascript/ql/src/semmle/javascript/ApiGraphs.qll diff --git a/javascript/ql/src/semmle/javascript/frameworks/SQL.qll b/javascript/ql/src/semmle/javascript/frameworks/SQL.qll index 2ee7d6a22852..abba43b2eac4 100644 --- a/javascript/ql/src/semmle/javascript/frameworks/SQL.qll +++ b/javascript/ql/src/semmle/javascript/frameworks/SQL.qll @@ -3,7 +3,6 @@ */ import javascript -private import ApiGraphs module SQL { /** A string-valued expression that is interpreted as a SQL command. */ diff --git a/javascript/ql/src/semmle/javascript/frameworks/SystemCommandExecutors.qll b/javascript/ql/src/semmle/javascript/frameworks/SystemCommandExecutors.qll index 8e2df4011a08..9f29ac2ceef0 100644 --- a/javascript/ql/src/semmle/javascript/frameworks/SystemCommandExecutors.qll +++ b/javascript/ql/src/semmle/javascript/frameworks/SystemCommandExecutors.qll @@ -4,7 +4,6 @@ */ import javascript -private import ApiGraphs private predicate execApi(string mod, string fn, int cmdArg, int optionsArg, boolean shell) { mod = "cross-spawn" and diff --git a/javascript/ql/src/semmle/javascript/security/dataflow/MissingRateLimiting.qll b/javascript/ql/src/semmle/javascript/security/dataflow/MissingRateLimiting.qll index 85638acd10f7..dd8224ab4764 100644 --- a/javascript/ql/src/semmle/javascript/security/dataflow/MissingRateLimiting.qll +++ b/javascript/ql/src/semmle/javascript/security/dataflow/MissingRateLimiting.qll @@ -24,7 +24,6 @@ import javascript private import semmle.javascript.frameworks.ConnectExpressShared::ConnectExpressShared -private import ApiGraphs // main concepts /** From f3173ca968554ce84a657293e2c128fc83b2f2d4 Mon Sep 17 00:00:00 2001 From: Max Schaefer Date: Thu, 3 Sep 2020 14:28:24 +0100 Subject: [PATCH 19/26] JavaScript: Add a few unit tests for API graphs. --- .../ApiGraphs/VerifyAssertions.expected | 4 + .../ApiGraphs/VerifyAssertions.ql | 118 ++++++++++++++++++ .../argprops/VerifyAssertions.expected | 0 .../ApiGraphs/argprops/VerifyAssertions.qlref | 1 + .../library-tests/ApiGraphs/argprops/index.js | 6 + .../ApiGraphs/argprops/package.json | 3 + .../async-await/VerifyAssertions.expected | 0 .../async-await/VerifyAssertions.qlref | 1 + .../ApiGraphs/async-await/index.js | 5 + .../ApiGraphs/async-await/package.json | 6 + .../branching-flow/VerifyAssertions.expected | 0 .../branching-flow/VerifyAssertions.qlref | 1 + .../ApiGraphs/branching-flow/index.js | 7 ++ .../ApiGraphs/branching-flow/package.json | 3 + .../classes/VerifyAssertions.expected | 0 .../ApiGraphs/classes/VerifyAssertions.qlref | 1 + .../ApiGraphs/classes/classes.js | 23 ++++ .../ApiGraphs/classes/package.json | 4 + .../ctor-arg/VerifyAssertions.expected | 0 .../ApiGraphs/ctor-arg/VerifyAssertions.qlref | 1 + .../library-tests/ApiGraphs/ctor-arg/index.js | 5 + .../ApiGraphs/ctor-arg/package.json | 3 + .../cyclic/VerifyAssertions.expected | 0 .../ApiGraphs/cyclic/VerifyAssertions.qlref | 1 + .../library-tests/ApiGraphs/cyclic/index.js | 4 + .../ApiGraphs/cyclic/package.json | 6 + .../VerifyAssertions.expected | 0 .../dynamic-prop-read/VerifyAssertions.qlref | 1 + .../ApiGraphs/dynamic-prop-read/index.js | 5 + .../ApiGraphs/dynamic-prop-read/package.json | 6 + .../VerifyAssertions.expected | 0 .../imprecise-export/VerifyAssertions.qlref | 1 + .../ApiGraphs/imprecise-export/index.js | 3 + .../ApiGraphs/imprecise-export/package.json | 3 + .../imprecision/VerifyAssertions.expected | 0 .../imprecision/VerifyAssertions.qlref | 1 + .../ApiGraphs/imprecision/index.js | 11 ++ .../ApiGraphs/imprecision/package.json | 3 + .../VerifyAssertions.expected | 0 .../namespaced-package/VerifyAssertions.qlref | 1 + .../ApiGraphs/namespaced-package/index.js | 2 + .../ApiGraphs/namespaced-package/package.json | 6 + .../VerifyAssertions.expected | 0 .../VerifyAssertions.qlref | 1 + .../ApiGraphs/nested-property-export/index.js | 7 ++ .../nested-property-export/package.json | 3 + .../nonlocal/VerifyAssertions.expected | 0 .../ApiGraphs/nonlocal/VerifyAssertions.qlref | 1 + .../library-tests/ApiGraphs/nonlocal/index.js | 15 +++ .../ApiGraphs/nonlocal/package.json | 6 + .../promises/VerifyAssertions.expected | 0 .../ApiGraphs/promises/VerifyAssertions.qlref | 1 + .../library-tests/ApiGraphs/promises/index.js | 21 ++++ .../ApiGraphs/promises/package.json | 6 + .../reexport/VerifyAssertions.expected | 1 + .../ApiGraphs/reexport/VerifyAssertions.qlref | 1 + .../library-tests/ApiGraphs/reexport/index.js | 6 + .../ApiGraphs/reexport/lib/impl.js | 3 + .../ApiGraphs/reexport/lib/utils.js | 3 + .../ApiGraphs/reexport/package.json | 3 + .../return-self/VerifyAssertions.expected | 0 .../return-self/VerifyAssertions.qlref | 1 + .../ApiGraphs/return-self/index.js | 6 + .../ApiGraphs/return-self/package.json | 3 + .../ApiGraphs/typed/VerifyAssertions.expected | 0 .../ApiGraphs/typed/VerifyAssertions.qlref | 1 + .../library-tests/ApiGraphs/typed/index.ts | 24 ++++ .../ApiGraphs/typed/package.json | 7 ++ .../library-tests/ApiGraphs/typed/shim.d.ts | 13 ++ .../ApiGraphs/typed/tsconfig.json | 6 + 70 files changed, 385 insertions(+) create mode 100644 javascript/ql/test/library-tests/ApiGraphs/VerifyAssertions.expected create mode 100644 javascript/ql/test/library-tests/ApiGraphs/VerifyAssertions.ql create mode 100644 javascript/ql/test/library-tests/ApiGraphs/argprops/VerifyAssertions.expected create mode 100644 javascript/ql/test/library-tests/ApiGraphs/argprops/VerifyAssertions.qlref create mode 100644 javascript/ql/test/library-tests/ApiGraphs/argprops/index.js create mode 100644 javascript/ql/test/library-tests/ApiGraphs/argprops/package.json create mode 100644 javascript/ql/test/library-tests/ApiGraphs/async-await/VerifyAssertions.expected create mode 100644 javascript/ql/test/library-tests/ApiGraphs/async-await/VerifyAssertions.qlref create mode 100644 javascript/ql/test/library-tests/ApiGraphs/async-await/index.js create mode 100644 javascript/ql/test/library-tests/ApiGraphs/async-await/package.json create mode 100644 javascript/ql/test/library-tests/ApiGraphs/branching-flow/VerifyAssertions.expected create mode 100644 javascript/ql/test/library-tests/ApiGraphs/branching-flow/VerifyAssertions.qlref create mode 100644 javascript/ql/test/library-tests/ApiGraphs/branching-flow/index.js create mode 100644 javascript/ql/test/library-tests/ApiGraphs/branching-flow/package.json create mode 100644 javascript/ql/test/library-tests/ApiGraphs/classes/VerifyAssertions.expected create mode 100644 javascript/ql/test/library-tests/ApiGraphs/classes/VerifyAssertions.qlref create mode 100644 javascript/ql/test/library-tests/ApiGraphs/classes/classes.js create mode 100644 javascript/ql/test/library-tests/ApiGraphs/classes/package.json create mode 100644 javascript/ql/test/library-tests/ApiGraphs/ctor-arg/VerifyAssertions.expected create mode 100644 javascript/ql/test/library-tests/ApiGraphs/ctor-arg/VerifyAssertions.qlref create mode 100644 javascript/ql/test/library-tests/ApiGraphs/ctor-arg/index.js create mode 100644 javascript/ql/test/library-tests/ApiGraphs/ctor-arg/package.json create mode 100644 javascript/ql/test/library-tests/ApiGraphs/cyclic/VerifyAssertions.expected create mode 100644 javascript/ql/test/library-tests/ApiGraphs/cyclic/VerifyAssertions.qlref create mode 100644 javascript/ql/test/library-tests/ApiGraphs/cyclic/index.js create mode 100644 javascript/ql/test/library-tests/ApiGraphs/cyclic/package.json create mode 100644 javascript/ql/test/library-tests/ApiGraphs/dynamic-prop-read/VerifyAssertions.expected create mode 100644 javascript/ql/test/library-tests/ApiGraphs/dynamic-prop-read/VerifyAssertions.qlref create mode 100644 javascript/ql/test/library-tests/ApiGraphs/dynamic-prop-read/index.js create mode 100644 javascript/ql/test/library-tests/ApiGraphs/dynamic-prop-read/package.json create mode 100644 javascript/ql/test/library-tests/ApiGraphs/imprecise-export/VerifyAssertions.expected create mode 100644 javascript/ql/test/library-tests/ApiGraphs/imprecise-export/VerifyAssertions.qlref create mode 100644 javascript/ql/test/library-tests/ApiGraphs/imprecise-export/index.js create mode 100644 javascript/ql/test/library-tests/ApiGraphs/imprecise-export/package.json create mode 100644 javascript/ql/test/library-tests/ApiGraphs/imprecision/VerifyAssertions.expected create mode 100644 javascript/ql/test/library-tests/ApiGraphs/imprecision/VerifyAssertions.qlref create mode 100644 javascript/ql/test/library-tests/ApiGraphs/imprecision/index.js create mode 100644 javascript/ql/test/library-tests/ApiGraphs/imprecision/package.json create mode 100644 javascript/ql/test/library-tests/ApiGraphs/namespaced-package/VerifyAssertions.expected create mode 100644 javascript/ql/test/library-tests/ApiGraphs/namespaced-package/VerifyAssertions.qlref create mode 100644 javascript/ql/test/library-tests/ApiGraphs/namespaced-package/index.js create mode 100644 javascript/ql/test/library-tests/ApiGraphs/namespaced-package/package.json create mode 100644 javascript/ql/test/library-tests/ApiGraphs/nested-property-export/VerifyAssertions.expected create mode 100644 javascript/ql/test/library-tests/ApiGraphs/nested-property-export/VerifyAssertions.qlref create mode 100644 javascript/ql/test/library-tests/ApiGraphs/nested-property-export/index.js create mode 100644 javascript/ql/test/library-tests/ApiGraphs/nested-property-export/package.json create mode 100644 javascript/ql/test/library-tests/ApiGraphs/nonlocal/VerifyAssertions.expected create mode 100644 javascript/ql/test/library-tests/ApiGraphs/nonlocal/VerifyAssertions.qlref create mode 100644 javascript/ql/test/library-tests/ApiGraphs/nonlocal/index.js create mode 100644 javascript/ql/test/library-tests/ApiGraphs/nonlocal/package.json create mode 100644 javascript/ql/test/library-tests/ApiGraphs/promises/VerifyAssertions.expected create mode 100644 javascript/ql/test/library-tests/ApiGraphs/promises/VerifyAssertions.qlref create mode 100644 javascript/ql/test/library-tests/ApiGraphs/promises/index.js create mode 100644 javascript/ql/test/library-tests/ApiGraphs/promises/package.json create mode 100644 javascript/ql/test/library-tests/ApiGraphs/reexport/VerifyAssertions.expected create mode 100644 javascript/ql/test/library-tests/ApiGraphs/reexport/VerifyAssertions.qlref create mode 100644 javascript/ql/test/library-tests/ApiGraphs/reexport/index.js create mode 100644 javascript/ql/test/library-tests/ApiGraphs/reexport/lib/impl.js create mode 100644 javascript/ql/test/library-tests/ApiGraphs/reexport/lib/utils.js create mode 100644 javascript/ql/test/library-tests/ApiGraphs/reexport/package.json create mode 100644 javascript/ql/test/library-tests/ApiGraphs/return-self/VerifyAssertions.expected create mode 100644 javascript/ql/test/library-tests/ApiGraphs/return-self/VerifyAssertions.qlref create mode 100644 javascript/ql/test/library-tests/ApiGraphs/return-self/index.js create mode 100644 javascript/ql/test/library-tests/ApiGraphs/return-self/package.json create mode 100644 javascript/ql/test/library-tests/ApiGraphs/typed/VerifyAssertions.expected create mode 100644 javascript/ql/test/library-tests/ApiGraphs/typed/VerifyAssertions.qlref create mode 100644 javascript/ql/test/library-tests/ApiGraphs/typed/index.ts create mode 100644 javascript/ql/test/library-tests/ApiGraphs/typed/package.json create mode 100644 javascript/ql/test/library-tests/ApiGraphs/typed/shim.d.ts create mode 100644 javascript/ql/test/library-tests/ApiGraphs/typed/tsconfig.json diff --git a/javascript/ql/test/library-tests/ApiGraphs/VerifyAssertions.expected b/javascript/ql/test/library-tests/ApiGraphs/VerifyAssertions.expected new file mode 100644 index 000000000000..6b9dcbfec500 --- /dev/null +++ b/javascript/ql/test/library-tests/ApiGraphs/VerifyAssertions.expected @@ -0,0 +1,4 @@ +| reexport/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. | +| typed/index.ts:14:36:14:106 | /* use ... )))) */ | use (module mongodb) has no outgoing edge labelled member Collection; it does have outgoing edges labelled member exports. | +| typed/index.ts:22:39:22:119 | /* def ... )))) */ | use (module mongoose) has no outgoing edge labelled member Model; it does have outgoing edges labelled member exports. | +| typed/index.ts:23:39:23:119 | /* def ... )))) */ | use (module mongoose) has no outgoing edge labelled member Query; it does have outgoing edges labelled member exports. | diff --git a/javascript/ql/test/library-tests/ApiGraphs/VerifyAssertions.ql b/javascript/ql/test/library-tests/ApiGraphs/VerifyAssertions.ql new file mode 100644 index 000000000000..6fba1d6e3b09 --- /dev/null +++ b/javascript/ql/test/library-tests/ApiGraphs/VerifyAssertions.ql @@ -0,0 +1,118 @@ +/** + * A test query that verifies assertions about the API graph embedded in source-code comments. + * + * An assertion is a comment of the form `def ` or `use `, 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 ` or `!use `, 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::Feature 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::Feature 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::Feature 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::Feature 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." + ) + ) + } +} + +from Assertion a +select a, a.explainFailure() diff --git a/javascript/ql/test/library-tests/ApiGraphs/argprops/VerifyAssertions.expected b/javascript/ql/test/library-tests/ApiGraphs/argprops/VerifyAssertions.expected new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/javascript/ql/test/library-tests/ApiGraphs/argprops/VerifyAssertions.qlref b/javascript/ql/test/library-tests/ApiGraphs/argprops/VerifyAssertions.qlref new file mode 100644 index 000000000000..47f26fcc174e --- /dev/null +++ b/javascript/ql/test/library-tests/ApiGraphs/argprops/VerifyAssertions.qlref @@ -0,0 +1 @@ +library-tests/ApiGraphs/VerifyAssertions.ql diff --git a/javascript/ql/test/library-tests/ApiGraphs/argprops/index.js b/javascript/ql/test/library-tests/ApiGraphs/argprops/index.js new file mode 100644 index 000000000000..8f7169081dc8 --- /dev/null +++ b/javascript/ql/test/library-tests/ApiGraphs/argprops/index.js @@ -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); diff --git a/javascript/ql/test/library-tests/ApiGraphs/argprops/package.json b/javascript/ql/test/library-tests/ApiGraphs/argprops/package.json new file mode 100644 index 000000000000..2a21d6294b3e --- /dev/null +++ b/javascript/ql/test/library-tests/ApiGraphs/argprops/package.json @@ -0,0 +1,3 @@ +{ + "name": "argprops" +} diff --git a/javascript/ql/test/library-tests/ApiGraphs/async-await/VerifyAssertions.expected b/javascript/ql/test/library-tests/ApiGraphs/async-await/VerifyAssertions.expected new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/javascript/ql/test/library-tests/ApiGraphs/async-await/VerifyAssertions.qlref b/javascript/ql/test/library-tests/ApiGraphs/async-await/VerifyAssertions.qlref new file mode 100644 index 000000000000..47f26fcc174e --- /dev/null +++ b/javascript/ql/test/library-tests/ApiGraphs/async-await/VerifyAssertions.qlref @@ -0,0 +1 @@ +library-tests/ApiGraphs/VerifyAssertions.ql diff --git a/javascript/ql/test/library-tests/ApiGraphs/async-await/index.js b/javascript/ql/test/library-tests/ApiGraphs/async-await/index.js new file mode 100644 index 000000000000..0c3b43461c1e --- /dev/null +++ b/javascript/ql/test/library-tests/ApiGraphs/async-await/index.js @@ -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))))) */ +}; diff --git a/javascript/ql/test/library-tests/ApiGraphs/async-await/package.json b/javascript/ql/test/library-tests/ApiGraphs/async-await/package.json new file mode 100644 index 000000000000..f4abf1711545 --- /dev/null +++ b/javascript/ql/test/library-tests/ApiGraphs/async-await/package.json @@ -0,0 +1,6 @@ +{ + "name": "async-await", + "dependencies": { + "fs-extra": "*" + } +} \ No newline at end of file diff --git a/javascript/ql/test/library-tests/ApiGraphs/branching-flow/VerifyAssertions.expected b/javascript/ql/test/library-tests/ApiGraphs/branching-flow/VerifyAssertions.expected new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/javascript/ql/test/library-tests/ApiGraphs/branching-flow/VerifyAssertions.qlref b/javascript/ql/test/library-tests/ApiGraphs/branching-flow/VerifyAssertions.qlref new file mode 100644 index 000000000000..47f26fcc174e --- /dev/null +++ b/javascript/ql/test/library-tests/ApiGraphs/branching-flow/VerifyAssertions.qlref @@ -0,0 +1 @@ +library-tests/ApiGraphs/VerifyAssertions.ql diff --git a/javascript/ql/test/library-tests/ApiGraphs/branching-flow/index.js b/javascript/ql/test/library-tests/ApiGraphs/branching-flow/index.js new file mode 100644 index 000000000000..8af9a7ac5864 --- /dev/null +++ b/javascript/ql/test/library-tests/ApiGraphs/branching-flow/index.js @@ -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))))) */ +}; \ No newline at end of file diff --git a/javascript/ql/test/library-tests/ApiGraphs/branching-flow/package.json b/javascript/ql/test/library-tests/ApiGraphs/branching-flow/package.json new file mode 100644 index 000000000000..7f366c52ed4c --- /dev/null +++ b/javascript/ql/test/library-tests/ApiGraphs/branching-flow/package.json @@ -0,0 +1,3 @@ +{ + "name": "branching-flow" +} \ No newline at end of file diff --git a/javascript/ql/test/library-tests/ApiGraphs/classes/VerifyAssertions.expected b/javascript/ql/test/library-tests/ApiGraphs/classes/VerifyAssertions.expected new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/javascript/ql/test/library-tests/ApiGraphs/classes/VerifyAssertions.qlref b/javascript/ql/test/library-tests/ApiGraphs/classes/VerifyAssertions.qlref new file mode 100644 index 000000000000..47f26fcc174e --- /dev/null +++ b/javascript/ql/test/library-tests/ApiGraphs/classes/VerifyAssertions.qlref @@ -0,0 +1 @@ +library-tests/ApiGraphs/VerifyAssertions.ql diff --git a/javascript/ql/test/library-tests/ApiGraphs/classes/classes.js b/javascript/ql/test/library-tests/ApiGraphs/classes/classes.js new file mode 100644 index 000000000000..4d6fab7ee373 --- /dev/null +++ b/javascript/ql/test/library-tests/ApiGraphs/classes/classes.js @@ -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; diff --git a/javascript/ql/test/library-tests/ApiGraphs/classes/package.json b/javascript/ql/test/library-tests/ApiGraphs/classes/package.json new file mode 100644 index 000000000000..b237b8db8fe9 --- /dev/null +++ b/javascript/ql/test/library-tests/ApiGraphs/classes/package.json @@ -0,0 +1,4 @@ +{ + "name": "classes", + "main": "./classes.js" +} diff --git a/javascript/ql/test/library-tests/ApiGraphs/ctor-arg/VerifyAssertions.expected b/javascript/ql/test/library-tests/ApiGraphs/ctor-arg/VerifyAssertions.expected new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/javascript/ql/test/library-tests/ApiGraphs/ctor-arg/VerifyAssertions.qlref b/javascript/ql/test/library-tests/ApiGraphs/ctor-arg/VerifyAssertions.qlref new file mode 100644 index 000000000000..47f26fcc174e --- /dev/null +++ b/javascript/ql/test/library-tests/ApiGraphs/ctor-arg/VerifyAssertions.qlref @@ -0,0 +1 @@ +library-tests/ApiGraphs/VerifyAssertions.ql diff --git a/javascript/ql/test/library-tests/ApiGraphs/ctor-arg/index.js b/javascript/ql/test/library-tests/ApiGraphs/ctor-arg/index.js new file mode 100644 index 000000000000..24f9f0d938e8 --- /dev/null +++ b/javascript/ql/test/library-tests/ApiGraphs/ctor-arg/index.js @@ -0,0 +1,5 @@ +export class A { + constructor(x) { /* use (parameter 0 (member A (member exports (module ctor-arg)))) */ + console.log(x); + } +} \ No newline at end of file diff --git a/javascript/ql/test/library-tests/ApiGraphs/ctor-arg/package.json b/javascript/ql/test/library-tests/ApiGraphs/ctor-arg/package.json new file mode 100644 index 000000000000..9cf34139f0af --- /dev/null +++ b/javascript/ql/test/library-tests/ApiGraphs/ctor-arg/package.json @@ -0,0 +1,3 @@ +{ + "name": "ctor-arg" +} \ No newline at end of file diff --git a/javascript/ql/test/library-tests/ApiGraphs/cyclic/VerifyAssertions.expected b/javascript/ql/test/library-tests/ApiGraphs/cyclic/VerifyAssertions.expected new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/javascript/ql/test/library-tests/ApiGraphs/cyclic/VerifyAssertions.qlref b/javascript/ql/test/library-tests/ApiGraphs/cyclic/VerifyAssertions.qlref new file mode 100644 index 000000000000..47f26fcc174e --- /dev/null +++ b/javascript/ql/test/library-tests/ApiGraphs/cyclic/VerifyAssertions.qlref @@ -0,0 +1 @@ +library-tests/ApiGraphs/VerifyAssertions.ql diff --git a/javascript/ql/test/library-tests/ApiGraphs/cyclic/index.js b/javascript/ql/test/library-tests/ApiGraphs/cyclic/index.js new file mode 100644 index 000000000000..81bea276eb89 --- /dev/null +++ b/javascript/ql/test/library-tests/ApiGraphs/cyclic/index.js @@ -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)))) */ diff --git a/javascript/ql/test/library-tests/ApiGraphs/cyclic/package.json b/javascript/ql/test/library-tests/ApiGraphs/cyclic/package.json new file mode 100644 index 000000000000..ebcde915f44e --- /dev/null +++ b/javascript/ql/test/library-tests/ApiGraphs/cyclic/package.json @@ -0,0 +1,6 @@ +{ + "name": "cyclic", + "dependencies": { + "foo": "*" + } +} diff --git a/javascript/ql/test/library-tests/ApiGraphs/dynamic-prop-read/VerifyAssertions.expected b/javascript/ql/test/library-tests/ApiGraphs/dynamic-prop-read/VerifyAssertions.expected new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/javascript/ql/test/library-tests/ApiGraphs/dynamic-prop-read/VerifyAssertions.qlref b/javascript/ql/test/library-tests/ApiGraphs/dynamic-prop-read/VerifyAssertions.qlref new file mode 100644 index 000000000000..47f26fcc174e --- /dev/null +++ b/javascript/ql/test/library-tests/ApiGraphs/dynamic-prop-read/VerifyAssertions.qlref @@ -0,0 +1 @@ +library-tests/ApiGraphs/VerifyAssertions.ql diff --git a/javascript/ql/test/library-tests/ApiGraphs/dynamic-prop-read/index.js b/javascript/ql/test/library-tests/ApiGraphs/dynamic-prop-read/index.js new file mode 100644 index 000000000000..2c76a4091c96 --- /dev/null +++ b/javascript/ql/test/library-tests/ApiGraphs/dynamic-prop-read/index.js @@ -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))))) */ \ No newline at end of file diff --git a/javascript/ql/test/library-tests/ApiGraphs/dynamic-prop-read/package.json b/javascript/ql/test/library-tests/ApiGraphs/dynamic-prop-read/package.json new file mode 100644 index 000000000000..2c83b5bf0d79 --- /dev/null +++ b/javascript/ql/test/library-tests/ApiGraphs/dynamic-prop-read/package.json @@ -0,0 +1,6 @@ +{ + "name": "dynamic-prop-read", + "dependencies": { + "classes": "*" + } +} \ No newline at end of file diff --git a/javascript/ql/test/library-tests/ApiGraphs/imprecise-export/VerifyAssertions.expected b/javascript/ql/test/library-tests/ApiGraphs/imprecise-export/VerifyAssertions.expected new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/javascript/ql/test/library-tests/ApiGraphs/imprecise-export/VerifyAssertions.qlref b/javascript/ql/test/library-tests/ApiGraphs/imprecise-export/VerifyAssertions.qlref new file mode 100644 index 000000000000..47f26fcc174e --- /dev/null +++ b/javascript/ql/test/library-tests/ApiGraphs/imprecise-export/VerifyAssertions.qlref @@ -0,0 +1 @@ +library-tests/ApiGraphs/VerifyAssertions.ql diff --git a/javascript/ql/test/library-tests/ApiGraphs/imprecise-export/index.js b/javascript/ql/test/library-tests/ApiGraphs/imprecise-export/index.js new file mode 100644 index 000000000000..a40722bb06a2 --- /dev/null +++ b/javascript/ql/test/library-tests/ApiGraphs/imprecise-export/index.js @@ -0,0 +1,3 @@ +anotherUnknownFunction().foo = 42; /* !def (member foo (member exports (module imprecise-export))) */ + +module.exports = unknownFunction(); diff --git a/javascript/ql/test/library-tests/ApiGraphs/imprecise-export/package.json b/javascript/ql/test/library-tests/ApiGraphs/imprecise-export/package.json new file mode 100644 index 000000000000..7554b8ae1169 --- /dev/null +++ b/javascript/ql/test/library-tests/ApiGraphs/imprecise-export/package.json @@ -0,0 +1,3 @@ +{ + "name": "imprecise-export" +} \ No newline at end of file diff --git a/javascript/ql/test/library-tests/ApiGraphs/imprecision/VerifyAssertions.expected b/javascript/ql/test/library-tests/ApiGraphs/imprecision/VerifyAssertions.expected new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/javascript/ql/test/library-tests/ApiGraphs/imprecision/VerifyAssertions.qlref b/javascript/ql/test/library-tests/ApiGraphs/imprecision/VerifyAssertions.qlref new file mode 100644 index 000000000000..47f26fcc174e --- /dev/null +++ b/javascript/ql/test/library-tests/ApiGraphs/imprecision/VerifyAssertions.qlref @@ -0,0 +1 @@ +library-tests/ApiGraphs/VerifyAssertions.ql diff --git a/javascript/ql/test/library-tests/ApiGraphs/imprecision/index.js b/javascript/ql/test/library-tests/ApiGraphs/imprecision/index.js new file mode 100644 index 000000000000..e8270e8d7e04 --- /dev/null +++ b/javascript/ql/test/library-tests/ApiGraphs/imprecision/index.js @@ -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)))))))) */; \ No newline at end of file diff --git a/javascript/ql/test/library-tests/ApiGraphs/imprecision/package.json b/javascript/ql/test/library-tests/ApiGraphs/imprecision/package.json new file mode 100644 index 000000000000..586715f89274 --- /dev/null +++ b/javascript/ql/test/library-tests/ApiGraphs/imprecision/package.json @@ -0,0 +1,3 @@ +{ + "name": "imprecision" +} \ No newline at end of file diff --git a/javascript/ql/test/library-tests/ApiGraphs/namespaced-package/VerifyAssertions.expected b/javascript/ql/test/library-tests/ApiGraphs/namespaced-package/VerifyAssertions.expected new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/javascript/ql/test/library-tests/ApiGraphs/namespaced-package/VerifyAssertions.qlref b/javascript/ql/test/library-tests/ApiGraphs/namespaced-package/VerifyAssertions.qlref new file mode 100644 index 000000000000..47f26fcc174e --- /dev/null +++ b/javascript/ql/test/library-tests/ApiGraphs/namespaced-package/VerifyAssertions.qlref @@ -0,0 +1 @@ +library-tests/ApiGraphs/VerifyAssertions.ql diff --git a/javascript/ql/test/library-tests/ApiGraphs/namespaced-package/index.js b/javascript/ql/test/library-tests/ApiGraphs/namespaced-package/index.js new file mode 100644 index 000000000000..4b3b20eac1d4 --- /dev/null +++ b/javascript/ql/test/library-tests/ApiGraphs/namespaced-package/index.js @@ -0,0 +1,2 @@ +import foo from "@myorg/myotherpkg"; +foo(); /* use (member default (member exports (module @myorg/myotherpkg))) */ diff --git a/javascript/ql/test/library-tests/ApiGraphs/namespaced-package/package.json b/javascript/ql/test/library-tests/ApiGraphs/namespaced-package/package.json new file mode 100644 index 000000000000..70a3c46e6ec2 --- /dev/null +++ b/javascript/ql/test/library-tests/ApiGraphs/namespaced-package/package.json @@ -0,0 +1,6 @@ +{ + "name": "@myorg/mypkg", + "dependencies": { + "@myorg/myotherpkg": "*" + } +} \ No newline at end of file diff --git a/javascript/ql/test/library-tests/ApiGraphs/nested-property-export/VerifyAssertions.expected b/javascript/ql/test/library-tests/ApiGraphs/nested-property-export/VerifyAssertions.expected new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/javascript/ql/test/library-tests/ApiGraphs/nested-property-export/VerifyAssertions.qlref b/javascript/ql/test/library-tests/ApiGraphs/nested-property-export/VerifyAssertions.qlref new file mode 100644 index 000000000000..47f26fcc174e --- /dev/null +++ b/javascript/ql/test/library-tests/ApiGraphs/nested-property-export/VerifyAssertions.qlref @@ -0,0 +1 @@ +library-tests/ApiGraphs/VerifyAssertions.ql diff --git a/javascript/ql/test/library-tests/ApiGraphs/nested-property-export/index.js b/javascript/ql/test/library-tests/ApiGraphs/nested-property-export/index.js new file mode 100644 index 000000000000..224f16295b48 --- /dev/null +++ b/javascript/ql/test/library-tests/ApiGraphs/nested-property-export/index.js @@ -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; +}; diff --git a/javascript/ql/test/library-tests/ApiGraphs/nested-property-export/package.json b/javascript/ql/test/library-tests/ApiGraphs/nested-property-export/package.json new file mode 100644 index 000000000000..57fc1e3e231b --- /dev/null +++ b/javascript/ql/test/library-tests/ApiGraphs/nested-property-export/package.json @@ -0,0 +1,3 @@ +{ + "name": "nested-property-export" +} \ No newline at end of file diff --git a/javascript/ql/test/library-tests/ApiGraphs/nonlocal/VerifyAssertions.expected b/javascript/ql/test/library-tests/ApiGraphs/nonlocal/VerifyAssertions.expected new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/javascript/ql/test/library-tests/ApiGraphs/nonlocal/VerifyAssertions.qlref b/javascript/ql/test/library-tests/ApiGraphs/nonlocal/VerifyAssertions.qlref new file mode 100644 index 000000000000..47f26fcc174e --- /dev/null +++ b/javascript/ql/test/library-tests/ApiGraphs/nonlocal/VerifyAssertions.qlref @@ -0,0 +1 @@ +library-tests/ApiGraphs/VerifyAssertions.ql diff --git a/javascript/ql/test/library-tests/ApiGraphs/nonlocal/index.js b/javascript/ql/test/library-tests/ApiGraphs/nonlocal/index.js new file mode 100644 index 000000000000..debbd554b90e --- /dev/null +++ b/javascript/ql/test/library-tests/ApiGraphs/nonlocal/index.js @@ -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))))) */ +); \ No newline at end of file diff --git a/javascript/ql/test/library-tests/ApiGraphs/nonlocal/package.json b/javascript/ql/test/library-tests/ApiGraphs/nonlocal/package.json new file mode 100644 index 000000000000..9ac64d76c1f5 --- /dev/null +++ b/javascript/ql/test/library-tests/ApiGraphs/nonlocal/package.json @@ -0,0 +1,6 @@ +{ + "name": "nonlocal", + "dependencies": { + "express": "*" + } +} diff --git a/javascript/ql/test/library-tests/ApiGraphs/promises/VerifyAssertions.expected b/javascript/ql/test/library-tests/ApiGraphs/promises/VerifyAssertions.expected new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/javascript/ql/test/library-tests/ApiGraphs/promises/VerifyAssertions.qlref b/javascript/ql/test/library-tests/ApiGraphs/promises/VerifyAssertions.qlref new file mode 100644 index 000000000000..47f26fcc174e --- /dev/null +++ b/javascript/ql/test/library-tests/ApiGraphs/promises/VerifyAssertions.qlref @@ -0,0 +1 @@ +library-tests/ApiGraphs/VerifyAssertions.ql diff --git a/javascript/ql/test/library-tests/ApiGraphs/promises/index.js b/javascript/ql/test/library-tests/ApiGraphs/promises/index.js new file mode 100644 index 000000000000..a3552dbdf643 --- /dev/null +++ b/javascript/ql/test/library-tests/ApiGraphs/promises/index.js @@ -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))))) */ + ); +}; \ No newline at end of file diff --git a/javascript/ql/test/library-tests/ApiGraphs/promises/package.json b/javascript/ql/test/library-tests/ApiGraphs/promises/package.json new file mode 100644 index 000000000000..992975b2a675 --- /dev/null +++ b/javascript/ql/test/library-tests/ApiGraphs/promises/package.json @@ -0,0 +1,6 @@ +{ + "name": "promises", + "dependencies": { + "fs-extra": "*" + } +} \ No newline at end of file diff --git a/javascript/ql/test/library-tests/ApiGraphs/reexport/VerifyAssertions.expected b/javascript/ql/test/library-tests/ApiGraphs/reexport/VerifyAssertions.expected new file mode 100644 index 000000000000..9815f2e44c84 --- /dev/null +++ b/javascript/ql/test/library-tests/ApiGraphs/reexport/VerifyAssertions.expected @@ -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. | diff --git a/javascript/ql/test/library-tests/ApiGraphs/reexport/VerifyAssertions.qlref b/javascript/ql/test/library-tests/ApiGraphs/reexport/VerifyAssertions.qlref new file mode 100644 index 000000000000..47f26fcc174e --- /dev/null +++ b/javascript/ql/test/library-tests/ApiGraphs/reexport/VerifyAssertions.qlref @@ -0,0 +1 @@ +library-tests/ApiGraphs/VerifyAssertions.ql diff --git a/javascript/ql/test/library-tests/ApiGraphs/reexport/index.js b/javascript/ql/test/library-tests/ApiGraphs/reexport/index.js new file mode 100644 index 000000000000..b88845d7175d --- /dev/null +++ b/javascript/ql/test/library-tests/ApiGraphs/reexport/index.js @@ -0,0 +1,6 @@ +const impl = require("./lib/impl.js"); + +module.exports = { + impl, + util: require("./lib/utils") +}; \ No newline at end of file diff --git a/javascript/ql/test/library-tests/ApiGraphs/reexport/lib/impl.js b/javascript/ql/test/library-tests/ApiGraphs/reexport/lib/impl.js new file mode 100644 index 000000000000..5c65e1cb6363 --- /dev/null +++ b/javascript/ql/test/library-tests/ApiGraphs/reexport/lib/impl.js @@ -0,0 +1,3 @@ +module.exports = function () { + return 42; /* def (return (member impl (member exports (module reexport)))) */ +}; diff --git a/javascript/ql/test/library-tests/ApiGraphs/reexport/lib/utils.js b/javascript/ql/test/library-tests/ApiGraphs/reexport/lib/utils.js new file mode 100644 index 000000000000..35110d694203 --- /dev/null +++ b/javascript/ql/test/library-tests/ApiGraphs/reexport/lib/utils.js @@ -0,0 +1,3 @@ +module.exports.id = function id(x) { /* use (parameter 0 (member id (member util (member exports (module reexport)))) */ + return x; +}; diff --git a/javascript/ql/test/library-tests/ApiGraphs/reexport/package.json b/javascript/ql/test/library-tests/ApiGraphs/reexport/package.json new file mode 100644 index 000000000000..b33f2e04bf8e --- /dev/null +++ b/javascript/ql/test/library-tests/ApiGraphs/reexport/package.json @@ -0,0 +1,3 @@ +{ + "name": "reexport" +} diff --git a/javascript/ql/test/library-tests/ApiGraphs/return-self/VerifyAssertions.expected b/javascript/ql/test/library-tests/ApiGraphs/return-self/VerifyAssertions.expected new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/javascript/ql/test/library-tests/ApiGraphs/return-self/VerifyAssertions.qlref b/javascript/ql/test/library-tests/ApiGraphs/return-self/VerifyAssertions.qlref new file mode 100644 index 000000000000..47f26fcc174e --- /dev/null +++ b/javascript/ql/test/library-tests/ApiGraphs/return-self/VerifyAssertions.qlref @@ -0,0 +1 @@ +library-tests/ApiGraphs/VerifyAssertions.ql diff --git a/javascript/ql/test/library-tests/ApiGraphs/return-self/index.js b/javascript/ql/test/library-tests/ApiGraphs/return-self/index.js new file mode 100644 index 000000000000..229adadfa0ae --- /dev/null +++ b/javascript/ql/test/library-tests/ApiGraphs/return-self/index.js @@ -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)))))) */ +} diff --git a/javascript/ql/test/library-tests/ApiGraphs/return-self/package.json b/javascript/ql/test/library-tests/ApiGraphs/return-self/package.json new file mode 100644 index 000000000000..807cd16edd6a --- /dev/null +++ b/javascript/ql/test/library-tests/ApiGraphs/return-self/package.json @@ -0,0 +1,3 @@ +{ + "name": "return-self" +} \ No newline at end of file diff --git a/javascript/ql/test/library-tests/ApiGraphs/typed/VerifyAssertions.expected b/javascript/ql/test/library-tests/ApiGraphs/typed/VerifyAssertions.expected new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/javascript/ql/test/library-tests/ApiGraphs/typed/VerifyAssertions.qlref b/javascript/ql/test/library-tests/ApiGraphs/typed/VerifyAssertions.qlref new file mode 100644 index 000000000000..47f26fcc174e --- /dev/null +++ b/javascript/ql/test/library-tests/ApiGraphs/typed/VerifyAssertions.qlref @@ -0,0 +1 @@ +library-tests/ApiGraphs/VerifyAssertions.ql diff --git a/javascript/ql/test/library-tests/ApiGraphs/typed/index.ts b/javascript/ql/test/library-tests/ApiGraphs/typed/index.ts new file mode 100644 index 000000000000..b89dac43aff1 --- /dev/null +++ b/javascript/ql/test/library-tests/ApiGraphs/typed/index.ts @@ -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))))) */ +}); diff --git a/javascript/ql/test/library-tests/ApiGraphs/typed/package.json b/javascript/ql/test/library-tests/ApiGraphs/typed/package.json new file mode 100644 index 000000000000..5862feda88fe --- /dev/null +++ b/javascript/ql/test/library-tests/ApiGraphs/typed/package.json @@ -0,0 +1,7 @@ +{ + "name": "typed", + "dependencies": { + "mongodb": "*", + "mongoose": "*" + } +} diff --git a/javascript/ql/test/library-tests/ApiGraphs/typed/shim.d.ts b/javascript/ql/test/library-tests/ApiGraphs/typed/shim.d.ts new file mode 100644 index 000000000000..2f3b2ce9b512 --- /dev/null +++ b/javascript/ql/test/library-tests/ApiGraphs/typed/shim.d.ts @@ -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; + } +} diff --git a/javascript/ql/test/library-tests/ApiGraphs/typed/tsconfig.json b/javascript/ql/test/library-tests/ApiGraphs/typed/tsconfig.json new file mode 100644 index 000000000000..4c06d765a041 --- /dev/null +++ b/javascript/ql/test/library-tests/ApiGraphs/typed/tsconfig.json @@ -0,0 +1,6 @@ +{ + "include": ["."], + "compilerOptions": { + "esModuleInterop": true + } +} From 58702e4c52b67ea1833b49c93000f6e4bb2eea09 Mon Sep 17 00:00:00 2001 From: Max Schaefer Date: Thu, 3 Sep 2020 16:14:31 +0100 Subject: [PATCH 20/26] JavaScript: Rename `EntryPoint.getADef` to `getARhs`. --- javascript/ql/src/semmle/javascript/ApiGraphs.qll | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/javascript/ql/src/semmle/javascript/ApiGraphs.qll b/javascript/ql/src/semmle/javascript/ApiGraphs.qll index 17dbe4fdc2b8..314dbf0c605b 100644 --- a/javascript/ql/src/semmle/javascript/ApiGraphs.qll +++ b/javascript/ql/src/semmle/javascript/ApiGraphs.qll @@ -255,7 +255,7 @@ module API { abstract DataFlow::SourceNode getAUse(); /** Gets a data-flow node that defines this entry point. */ - abstract DataFlow::Node getADef(); + abstract DataFlow::Node getARhs(); } /** @@ -363,7 +363,7 @@ module API { hasSemantics(rhs) and ( base = MkRoot() and - rhs = lbl.(EntryPoint).getADef() + rhs = lbl.(EntryPoint).getARhs() or exists(string m, string prop | base = MkModuleExport(m) and From cb433a0c0fb5f6c715694f5b82eded3d7ce25af2 Mon Sep 17 00:00:00 2001 From: Max Schaefer Date: Thu, 3 Sep 2020 16:15:48 +0100 Subject: [PATCH 21/26] JavaScript: Add test for custom API-graph entry points. --- .../ql/test/library-tests/ApiGraphs/VerifyAssertions.ql | 8 ++++++++ .../custom-entry-point/VerifyAssertions.expected | 0 .../ApiGraphs/custom-entry-point/VerifyAssertions.qlref | 1 + .../library-tests/ApiGraphs/custom-entry-point/index.js | 1 + .../ApiGraphs/custom-entry-point/package.json | 3 +++ 5 files changed, 13 insertions(+) create mode 100644 javascript/ql/test/library-tests/ApiGraphs/custom-entry-point/VerifyAssertions.expected create mode 100644 javascript/ql/test/library-tests/ApiGraphs/custom-entry-point/VerifyAssertions.qlref create mode 100644 javascript/ql/test/library-tests/ApiGraphs/custom-entry-point/index.js create mode 100644 javascript/ql/test/library-tests/ApiGraphs/custom-entry-point/package.json diff --git a/javascript/ql/test/library-tests/ApiGraphs/VerifyAssertions.ql b/javascript/ql/test/library-tests/ApiGraphs/VerifyAssertions.ql index 6fba1d6e3b09..422c773999b3 100644 --- a/javascript/ql/test/library-tests/ApiGraphs/VerifyAssertions.ql +++ b/javascript/ql/test/library-tests/ApiGraphs/VerifyAssertions.ql @@ -20,6 +20,14 @@ import javascript +class CustomEntryPoint extends API::EntryPoint { + CustomEntryPoint() { this = "CustomEntryPoint" } + + override DataFlow::SourceNode getAUse() { result = DataFlow::globalVarRef("CustomEntryPoint") } + + override DataFlow::Node getARhs() { none() } +} + private DataFlow::Node getNode(API::Feature nd, string kind) { kind = "def" and result = nd.getARhs() diff --git a/javascript/ql/test/library-tests/ApiGraphs/custom-entry-point/VerifyAssertions.expected b/javascript/ql/test/library-tests/ApiGraphs/custom-entry-point/VerifyAssertions.expected new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/javascript/ql/test/library-tests/ApiGraphs/custom-entry-point/VerifyAssertions.qlref b/javascript/ql/test/library-tests/ApiGraphs/custom-entry-point/VerifyAssertions.qlref new file mode 100644 index 000000000000..47f26fcc174e --- /dev/null +++ b/javascript/ql/test/library-tests/ApiGraphs/custom-entry-point/VerifyAssertions.qlref @@ -0,0 +1 @@ +library-tests/ApiGraphs/VerifyAssertions.ql diff --git a/javascript/ql/test/library-tests/ApiGraphs/custom-entry-point/index.js b/javascript/ql/test/library-tests/ApiGraphs/custom-entry-point/index.js new file mode 100644 index 000000000000..43819d6ef6b2 --- /dev/null +++ b/javascript/ql/test/library-tests/ApiGraphs/custom-entry-point/index.js @@ -0,0 +1 @@ +module.exports = CustomEntryPoint.foo; /* use (member foo (CustomEntryPoint)) */ diff --git a/javascript/ql/test/library-tests/ApiGraphs/custom-entry-point/package.json b/javascript/ql/test/library-tests/ApiGraphs/custom-entry-point/package.json new file mode 100644 index 000000000000..902d9f087d36 --- /dev/null +++ b/javascript/ql/test/library-tests/ApiGraphs/custom-entry-point/package.json @@ -0,0 +1,3 @@ +{ + "name": "custom-entry-point" +} \ No newline at end of file From 252902d245d9ff62620b25e494a4bf638168d3a2 Mon Sep 17 00:00:00 2001 From: Max Schaefer Date: Thu, 3 Sep 2020 20:11:19 +0100 Subject: [PATCH 22/26] JavaScript: Restructure API-graph tests. With the old test runner we cannot have `VerifyAssertions.qlref`s for each individual test that reference a shared `VerifyAssertions.ql` in the parent directory, since it doesn't like nested tests. Instead, we have to turn `VerifyAssertions.ql` into `VerifyAssertions.qll`, and each `VerifyAsssertions.qlref` into a `VerifyAssertions.ql` that imports it. But then that doesn't work with our old directory structure, since the import path would have to contain the invalid identifier `library-tests`. As a workaround, I have moved the API graph tests into a directory without dashes in its path. --- .../VerifyAssertions.qll} | 11 +---------- .../ApiGraphs/argprops/VerifyAssertions.expected | 0 .../ql/test/ApiGraphs/argprops/VerifyAssertions.ql | 1 + .../{library-tests => }/ApiGraphs/argprops/index.js | 0 .../ApiGraphs/argprops/package.json | 0 .../ApiGraphs/async-await/VerifyAssertions.expected | 0 .../ql/test/ApiGraphs/async-await/VerifyAssertions.ql | 1 + .../ApiGraphs/async-await/index.js | 0 .../ApiGraphs/async-await/package.json | 0 .../branching-flow/VerifyAssertions.expected | 0 .../test/ApiGraphs/branching-flow/VerifyAssertions.ql | 1 + .../ApiGraphs/branching-flow/index.js | 0 .../ApiGraphs/branching-flow/package.json | 0 .../ApiGraphs/classes/VerifyAssertions.expected | 0 .../ql/test/ApiGraphs/classes/VerifyAssertions.ql | 1 + .../{library-tests => }/ApiGraphs/classes/classes.js | 0 .../ApiGraphs/classes/package.json | 0 .../ApiGraphs/ctor-arg/VerifyAssertions.expected | 0 .../ql/test/ApiGraphs/ctor-arg/VerifyAssertions.ql | 1 + .../{library-tests => }/ApiGraphs/ctor-arg/index.js | 0 .../ApiGraphs/ctor-arg/package.json | 0 .../custom-entry-point/VerifyAssertions.expected | 0 .../ApiGraphs/custom-entry-point/VerifyAssertions.ql | 9 +++++++++ .../ApiGraphs/custom-entry-point/index.js | 0 .../ApiGraphs/custom-entry-point/package.json | 0 .../ApiGraphs/cyclic/VerifyAssertions.expected | 0 .../ql/test/ApiGraphs/cyclic/VerifyAssertions.ql | 1 + .../{library-tests => }/ApiGraphs/cyclic/index.js | 0 .../{library-tests => }/ApiGraphs/cyclic/package.json | 0 .../dynamic-prop-read/VerifyAssertions.expected | 0 .../ApiGraphs/dynamic-prop-read/VerifyAssertions.ql | 1 + .../ApiGraphs/dynamic-prop-read/index.js | 0 .../ApiGraphs/dynamic-prop-read/package.json | 0 .../imprecise-export/VerifyAssertions.expected | 0 .../ApiGraphs/imprecise-export/VerifyAssertions.ql | 1 + .../ApiGraphs/imprecise-export/index.js | 0 .../ApiGraphs/imprecise-export/package.json | 0 .../ApiGraphs/imprecision/VerifyAssertions.expected | 0 .../ql/test/ApiGraphs/imprecision/VerifyAssertions.ql | 1 + .../ApiGraphs/imprecision/index.js | 0 .../ApiGraphs/imprecision/package.json | 0 .../namespaced-package/VerifyAssertions.expected | 0 .../ApiGraphs/namespaced-package/VerifyAssertions.ql | 1 + .../ApiGraphs/namespaced-package/index.js | 0 .../ApiGraphs/namespaced-package/package.json | 0 .../nested-property-export/VerifyAssertions.expected | 0 .../nested-property-export/VerifyAssertions.ql | 1 + .../ApiGraphs/nested-property-export/index.js | 0 .../ApiGraphs/nested-property-export/package.json | 0 .../ApiGraphs/nonlocal/VerifyAssertions.expected | 0 .../ql/test/ApiGraphs/nonlocal/VerifyAssertions.ql | 1 + .../{library-tests => }/ApiGraphs/nonlocal/index.js | 0 .../ApiGraphs/nonlocal/package.json | 0 .../ApiGraphs/promises/VerifyAssertions.expected | 0 .../ql/test/ApiGraphs/promises/VerifyAssertions.ql | 1 + .../{library-tests => }/ApiGraphs/promises/index.js | 0 .../ApiGraphs/promises/package.json | 0 .../ApiGraphs/reexport/VerifyAssertions.expected | 0 .../ql/test/ApiGraphs/reexport/VerifyAssertions.ql | 1 + .../{library-tests => }/ApiGraphs/reexport/index.js | 0 .../ApiGraphs/reexport/lib/impl.js | 0 .../ApiGraphs/reexport/lib/utils.js | 0 .../ApiGraphs/reexport/package.json | 0 .../ApiGraphs/return-self/VerifyAssertions.expected | 0 .../ql/test/ApiGraphs/return-self/VerifyAssertions.ql | 1 + .../ApiGraphs/return-self/index.js | 0 .../ApiGraphs/return-self/package.json | 0 .../ApiGraphs/typed/VerifyAssertions.expected | 0 .../ql/test/ApiGraphs/typed/VerifyAssertions.ql | 1 + .../test/{library-tests => }/ApiGraphs/typed/index.ts | 0 .../{library-tests => }/ApiGraphs/typed/package.json | 0 .../{library-tests => }/ApiGraphs/typed/shim.d.ts | 0 .../{library-tests => }/ApiGraphs/typed/tsconfig.json | 0 .../library-tests/ApiGraphs/VerifyAssertions.expected | 4 ---- .../ApiGraphs/argprops/VerifyAssertions.qlref | 1 - .../ApiGraphs/async-await/VerifyAssertions.qlref | 1 - .../ApiGraphs/branching-flow/VerifyAssertions.qlref | 1 - .../ApiGraphs/classes/VerifyAssertions.qlref | 1 - .../ApiGraphs/ctor-arg/VerifyAssertions.qlref | 1 - .../custom-entry-point/VerifyAssertions.qlref | 1 - .../ApiGraphs/cyclic/VerifyAssertions.qlref | 1 - .../dynamic-prop-read/VerifyAssertions.qlref | 1 - .../ApiGraphs/imprecise-export/VerifyAssertions.qlref | 1 - .../ApiGraphs/imprecision/VerifyAssertions.qlref | 1 - .../namespaced-package/VerifyAssertions.qlref | 1 - .../nested-property-export/VerifyAssertions.qlref | 1 - .../ApiGraphs/nonlocal/VerifyAssertions.qlref | 1 - .../ApiGraphs/promises/VerifyAssertions.qlref | 1 - .../ApiGraphs/reexport/VerifyAssertions.qlref | 1 - .../ApiGraphs/return-self/VerifyAssertions.qlref | 1 - .../ApiGraphs/typed/VerifyAssertions.qlref | 1 - 91 files changed, 26 insertions(+), 31 deletions(-) rename javascript/ql/test/{library-tests/ApiGraphs/VerifyAssertions.ql => ApiGraphs/VerifyAssertions.qll} (92%) rename javascript/ql/test/{library-tests => }/ApiGraphs/argprops/VerifyAssertions.expected (100%) create mode 100644 javascript/ql/test/ApiGraphs/argprops/VerifyAssertions.ql rename javascript/ql/test/{library-tests => }/ApiGraphs/argprops/index.js (100%) rename javascript/ql/test/{library-tests => }/ApiGraphs/argprops/package.json (100%) rename javascript/ql/test/{library-tests => }/ApiGraphs/async-await/VerifyAssertions.expected (100%) create mode 100644 javascript/ql/test/ApiGraphs/async-await/VerifyAssertions.ql rename javascript/ql/test/{library-tests => }/ApiGraphs/async-await/index.js (100%) rename javascript/ql/test/{library-tests => }/ApiGraphs/async-await/package.json (100%) rename javascript/ql/test/{library-tests => }/ApiGraphs/branching-flow/VerifyAssertions.expected (100%) create mode 100644 javascript/ql/test/ApiGraphs/branching-flow/VerifyAssertions.ql rename javascript/ql/test/{library-tests => }/ApiGraphs/branching-flow/index.js (100%) rename javascript/ql/test/{library-tests => }/ApiGraphs/branching-flow/package.json (100%) rename javascript/ql/test/{library-tests => }/ApiGraphs/classes/VerifyAssertions.expected (100%) create mode 100644 javascript/ql/test/ApiGraphs/classes/VerifyAssertions.ql rename javascript/ql/test/{library-tests => }/ApiGraphs/classes/classes.js (100%) rename javascript/ql/test/{library-tests => }/ApiGraphs/classes/package.json (100%) rename javascript/ql/test/{library-tests => }/ApiGraphs/ctor-arg/VerifyAssertions.expected (100%) create mode 100644 javascript/ql/test/ApiGraphs/ctor-arg/VerifyAssertions.ql rename javascript/ql/test/{library-tests => }/ApiGraphs/ctor-arg/index.js (100%) rename javascript/ql/test/{library-tests => }/ApiGraphs/ctor-arg/package.json (100%) rename javascript/ql/test/{library-tests => }/ApiGraphs/custom-entry-point/VerifyAssertions.expected (100%) create mode 100644 javascript/ql/test/ApiGraphs/custom-entry-point/VerifyAssertions.ql rename javascript/ql/test/{library-tests => }/ApiGraphs/custom-entry-point/index.js (100%) rename javascript/ql/test/{library-tests => }/ApiGraphs/custom-entry-point/package.json (100%) rename javascript/ql/test/{library-tests => }/ApiGraphs/cyclic/VerifyAssertions.expected (100%) create mode 100644 javascript/ql/test/ApiGraphs/cyclic/VerifyAssertions.ql rename javascript/ql/test/{library-tests => }/ApiGraphs/cyclic/index.js (100%) rename javascript/ql/test/{library-tests => }/ApiGraphs/cyclic/package.json (100%) rename javascript/ql/test/{library-tests => }/ApiGraphs/dynamic-prop-read/VerifyAssertions.expected (100%) create mode 100644 javascript/ql/test/ApiGraphs/dynamic-prop-read/VerifyAssertions.ql rename javascript/ql/test/{library-tests => }/ApiGraphs/dynamic-prop-read/index.js (100%) rename javascript/ql/test/{library-tests => }/ApiGraphs/dynamic-prop-read/package.json (100%) rename javascript/ql/test/{library-tests => }/ApiGraphs/imprecise-export/VerifyAssertions.expected (100%) create mode 100644 javascript/ql/test/ApiGraphs/imprecise-export/VerifyAssertions.ql rename javascript/ql/test/{library-tests => }/ApiGraphs/imprecise-export/index.js (100%) rename javascript/ql/test/{library-tests => }/ApiGraphs/imprecise-export/package.json (100%) rename javascript/ql/test/{library-tests => }/ApiGraphs/imprecision/VerifyAssertions.expected (100%) create mode 100644 javascript/ql/test/ApiGraphs/imprecision/VerifyAssertions.ql rename javascript/ql/test/{library-tests => }/ApiGraphs/imprecision/index.js (100%) rename javascript/ql/test/{library-tests => }/ApiGraphs/imprecision/package.json (100%) rename javascript/ql/test/{library-tests => }/ApiGraphs/namespaced-package/VerifyAssertions.expected (100%) create mode 100644 javascript/ql/test/ApiGraphs/namespaced-package/VerifyAssertions.ql rename javascript/ql/test/{library-tests => }/ApiGraphs/namespaced-package/index.js (100%) rename javascript/ql/test/{library-tests => }/ApiGraphs/namespaced-package/package.json (100%) rename javascript/ql/test/{library-tests => }/ApiGraphs/nested-property-export/VerifyAssertions.expected (100%) create mode 100644 javascript/ql/test/ApiGraphs/nested-property-export/VerifyAssertions.ql rename javascript/ql/test/{library-tests => }/ApiGraphs/nested-property-export/index.js (100%) rename javascript/ql/test/{library-tests => }/ApiGraphs/nested-property-export/package.json (100%) rename javascript/ql/test/{library-tests => }/ApiGraphs/nonlocal/VerifyAssertions.expected (100%) create mode 100644 javascript/ql/test/ApiGraphs/nonlocal/VerifyAssertions.ql rename javascript/ql/test/{library-tests => }/ApiGraphs/nonlocal/index.js (100%) rename javascript/ql/test/{library-tests => }/ApiGraphs/nonlocal/package.json (100%) rename javascript/ql/test/{library-tests => }/ApiGraphs/promises/VerifyAssertions.expected (100%) create mode 100644 javascript/ql/test/ApiGraphs/promises/VerifyAssertions.ql rename javascript/ql/test/{library-tests => }/ApiGraphs/promises/index.js (100%) rename javascript/ql/test/{library-tests => }/ApiGraphs/promises/package.json (100%) rename javascript/ql/test/{library-tests => }/ApiGraphs/reexport/VerifyAssertions.expected (100%) create mode 100644 javascript/ql/test/ApiGraphs/reexport/VerifyAssertions.ql rename javascript/ql/test/{library-tests => }/ApiGraphs/reexport/index.js (100%) rename javascript/ql/test/{library-tests => }/ApiGraphs/reexport/lib/impl.js (100%) rename javascript/ql/test/{library-tests => }/ApiGraphs/reexport/lib/utils.js (100%) rename javascript/ql/test/{library-tests => }/ApiGraphs/reexport/package.json (100%) rename javascript/ql/test/{library-tests => }/ApiGraphs/return-self/VerifyAssertions.expected (100%) create mode 100644 javascript/ql/test/ApiGraphs/return-self/VerifyAssertions.ql rename javascript/ql/test/{library-tests => }/ApiGraphs/return-self/index.js (100%) rename javascript/ql/test/{library-tests => }/ApiGraphs/return-self/package.json (100%) rename javascript/ql/test/{library-tests => }/ApiGraphs/typed/VerifyAssertions.expected (100%) create mode 100644 javascript/ql/test/ApiGraphs/typed/VerifyAssertions.ql rename javascript/ql/test/{library-tests => }/ApiGraphs/typed/index.ts (100%) rename javascript/ql/test/{library-tests => }/ApiGraphs/typed/package.json (100%) rename javascript/ql/test/{library-tests => }/ApiGraphs/typed/shim.d.ts (100%) rename javascript/ql/test/{library-tests => }/ApiGraphs/typed/tsconfig.json (100%) delete mode 100644 javascript/ql/test/library-tests/ApiGraphs/VerifyAssertions.expected delete mode 100644 javascript/ql/test/library-tests/ApiGraphs/argprops/VerifyAssertions.qlref delete mode 100644 javascript/ql/test/library-tests/ApiGraphs/async-await/VerifyAssertions.qlref delete mode 100644 javascript/ql/test/library-tests/ApiGraphs/branching-flow/VerifyAssertions.qlref delete mode 100644 javascript/ql/test/library-tests/ApiGraphs/classes/VerifyAssertions.qlref delete mode 100644 javascript/ql/test/library-tests/ApiGraphs/ctor-arg/VerifyAssertions.qlref delete mode 100644 javascript/ql/test/library-tests/ApiGraphs/custom-entry-point/VerifyAssertions.qlref delete mode 100644 javascript/ql/test/library-tests/ApiGraphs/cyclic/VerifyAssertions.qlref delete mode 100644 javascript/ql/test/library-tests/ApiGraphs/dynamic-prop-read/VerifyAssertions.qlref delete mode 100644 javascript/ql/test/library-tests/ApiGraphs/imprecise-export/VerifyAssertions.qlref delete mode 100644 javascript/ql/test/library-tests/ApiGraphs/imprecision/VerifyAssertions.qlref delete mode 100644 javascript/ql/test/library-tests/ApiGraphs/namespaced-package/VerifyAssertions.qlref delete mode 100644 javascript/ql/test/library-tests/ApiGraphs/nested-property-export/VerifyAssertions.qlref delete mode 100644 javascript/ql/test/library-tests/ApiGraphs/nonlocal/VerifyAssertions.qlref delete mode 100644 javascript/ql/test/library-tests/ApiGraphs/promises/VerifyAssertions.qlref delete mode 100644 javascript/ql/test/library-tests/ApiGraphs/reexport/VerifyAssertions.qlref delete mode 100644 javascript/ql/test/library-tests/ApiGraphs/return-self/VerifyAssertions.qlref delete mode 100644 javascript/ql/test/library-tests/ApiGraphs/typed/VerifyAssertions.qlref diff --git a/javascript/ql/test/library-tests/ApiGraphs/VerifyAssertions.ql b/javascript/ql/test/ApiGraphs/VerifyAssertions.qll similarity index 92% rename from javascript/ql/test/library-tests/ApiGraphs/VerifyAssertions.ql rename to javascript/ql/test/ApiGraphs/VerifyAssertions.qll index 422c773999b3..489a94986a13 100644 --- a/javascript/ql/test/library-tests/ApiGraphs/VerifyAssertions.ql +++ b/javascript/ql/test/ApiGraphs/VerifyAssertions.qll @@ -20,14 +20,6 @@ import javascript -class CustomEntryPoint extends API::EntryPoint { - CustomEntryPoint() { this = "CustomEntryPoint" } - - override DataFlow::SourceNode getAUse() { result = DataFlow::globalVarRef("CustomEntryPoint") } - - override DataFlow::Node getARhs() { none() } -} - private DataFlow::Node getNode(API::Feature nd, string kind) { kind = "def" and result = nd.getARhs() @@ -122,5 +114,4 @@ class Assertion extends Comment { } } -from Assertion a -select a, a.explainFailure() +query predicate failed(Assertion a, string explanation) { explanation = a.explainFailure() } diff --git a/javascript/ql/test/library-tests/ApiGraphs/argprops/VerifyAssertions.expected b/javascript/ql/test/ApiGraphs/argprops/VerifyAssertions.expected similarity index 100% rename from javascript/ql/test/library-tests/ApiGraphs/argprops/VerifyAssertions.expected rename to javascript/ql/test/ApiGraphs/argprops/VerifyAssertions.expected diff --git a/javascript/ql/test/ApiGraphs/argprops/VerifyAssertions.ql b/javascript/ql/test/ApiGraphs/argprops/VerifyAssertions.ql new file mode 100644 index 000000000000..b9c54e26072d --- /dev/null +++ b/javascript/ql/test/ApiGraphs/argprops/VerifyAssertions.ql @@ -0,0 +1 @@ +import ApiGraphs.VerifyAssertions diff --git a/javascript/ql/test/library-tests/ApiGraphs/argprops/index.js b/javascript/ql/test/ApiGraphs/argprops/index.js similarity index 100% rename from javascript/ql/test/library-tests/ApiGraphs/argprops/index.js rename to javascript/ql/test/ApiGraphs/argprops/index.js diff --git a/javascript/ql/test/library-tests/ApiGraphs/argprops/package.json b/javascript/ql/test/ApiGraphs/argprops/package.json similarity index 100% rename from javascript/ql/test/library-tests/ApiGraphs/argprops/package.json rename to javascript/ql/test/ApiGraphs/argprops/package.json diff --git a/javascript/ql/test/library-tests/ApiGraphs/async-await/VerifyAssertions.expected b/javascript/ql/test/ApiGraphs/async-await/VerifyAssertions.expected similarity index 100% rename from javascript/ql/test/library-tests/ApiGraphs/async-await/VerifyAssertions.expected rename to javascript/ql/test/ApiGraphs/async-await/VerifyAssertions.expected diff --git a/javascript/ql/test/ApiGraphs/async-await/VerifyAssertions.ql b/javascript/ql/test/ApiGraphs/async-await/VerifyAssertions.ql new file mode 100644 index 000000000000..b9c54e26072d --- /dev/null +++ b/javascript/ql/test/ApiGraphs/async-await/VerifyAssertions.ql @@ -0,0 +1 @@ +import ApiGraphs.VerifyAssertions diff --git a/javascript/ql/test/library-tests/ApiGraphs/async-await/index.js b/javascript/ql/test/ApiGraphs/async-await/index.js similarity index 100% rename from javascript/ql/test/library-tests/ApiGraphs/async-await/index.js rename to javascript/ql/test/ApiGraphs/async-await/index.js diff --git a/javascript/ql/test/library-tests/ApiGraphs/async-await/package.json b/javascript/ql/test/ApiGraphs/async-await/package.json similarity index 100% rename from javascript/ql/test/library-tests/ApiGraphs/async-await/package.json rename to javascript/ql/test/ApiGraphs/async-await/package.json diff --git a/javascript/ql/test/library-tests/ApiGraphs/branching-flow/VerifyAssertions.expected b/javascript/ql/test/ApiGraphs/branching-flow/VerifyAssertions.expected similarity index 100% rename from javascript/ql/test/library-tests/ApiGraphs/branching-flow/VerifyAssertions.expected rename to javascript/ql/test/ApiGraphs/branching-flow/VerifyAssertions.expected diff --git a/javascript/ql/test/ApiGraphs/branching-flow/VerifyAssertions.ql b/javascript/ql/test/ApiGraphs/branching-flow/VerifyAssertions.ql new file mode 100644 index 000000000000..b9c54e26072d --- /dev/null +++ b/javascript/ql/test/ApiGraphs/branching-flow/VerifyAssertions.ql @@ -0,0 +1 @@ +import ApiGraphs.VerifyAssertions diff --git a/javascript/ql/test/library-tests/ApiGraphs/branching-flow/index.js b/javascript/ql/test/ApiGraphs/branching-flow/index.js similarity index 100% rename from javascript/ql/test/library-tests/ApiGraphs/branching-flow/index.js rename to javascript/ql/test/ApiGraphs/branching-flow/index.js diff --git a/javascript/ql/test/library-tests/ApiGraphs/branching-flow/package.json b/javascript/ql/test/ApiGraphs/branching-flow/package.json similarity index 100% rename from javascript/ql/test/library-tests/ApiGraphs/branching-flow/package.json rename to javascript/ql/test/ApiGraphs/branching-flow/package.json diff --git a/javascript/ql/test/library-tests/ApiGraphs/classes/VerifyAssertions.expected b/javascript/ql/test/ApiGraphs/classes/VerifyAssertions.expected similarity index 100% rename from javascript/ql/test/library-tests/ApiGraphs/classes/VerifyAssertions.expected rename to javascript/ql/test/ApiGraphs/classes/VerifyAssertions.expected diff --git a/javascript/ql/test/ApiGraphs/classes/VerifyAssertions.ql b/javascript/ql/test/ApiGraphs/classes/VerifyAssertions.ql new file mode 100644 index 000000000000..b9c54e26072d --- /dev/null +++ b/javascript/ql/test/ApiGraphs/classes/VerifyAssertions.ql @@ -0,0 +1 @@ +import ApiGraphs.VerifyAssertions diff --git a/javascript/ql/test/library-tests/ApiGraphs/classes/classes.js b/javascript/ql/test/ApiGraphs/classes/classes.js similarity index 100% rename from javascript/ql/test/library-tests/ApiGraphs/classes/classes.js rename to javascript/ql/test/ApiGraphs/classes/classes.js diff --git a/javascript/ql/test/library-tests/ApiGraphs/classes/package.json b/javascript/ql/test/ApiGraphs/classes/package.json similarity index 100% rename from javascript/ql/test/library-tests/ApiGraphs/classes/package.json rename to javascript/ql/test/ApiGraphs/classes/package.json diff --git a/javascript/ql/test/library-tests/ApiGraphs/ctor-arg/VerifyAssertions.expected b/javascript/ql/test/ApiGraphs/ctor-arg/VerifyAssertions.expected similarity index 100% rename from javascript/ql/test/library-tests/ApiGraphs/ctor-arg/VerifyAssertions.expected rename to javascript/ql/test/ApiGraphs/ctor-arg/VerifyAssertions.expected diff --git a/javascript/ql/test/ApiGraphs/ctor-arg/VerifyAssertions.ql b/javascript/ql/test/ApiGraphs/ctor-arg/VerifyAssertions.ql new file mode 100644 index 000000000000..b9c54e26072d --- /dev/null +++ b/javascript/ql/test/ApiGraphs/ctor-arg/VerifyAssertions.ql @@ -0,0 +1 @@ +import ApiGraphs.VerifyAssertions diff --git a/javascript/ql/test/library-tests/ApiGraphs/ctor-arg/index.js b/javascript/ql/test/ApiGraphs/ctor-arg/index.js similarity index 100% rename from javascript/ql/test/library-tests/ApiGraphs/ctor-arg/index.js rename to javascript/ql/test/ApiGraphs/ctor-arg/index.js diff --git a/javascript/ql/test/library-tests/ApiGraphs/ctor-arg/package.json b/javascript/ql/test/ApiGraphs/ctor-arg/package.json similarity index 100% rename from javascript/ql/test/library-tests/ApiGraphs/ctor-arg/package.json rename to javascript/ql/test/ApiGraphs/ctor-arg/package.json diff --git a/javascript/ql/test/library-tests/ApiGraphs/custom-entry-point/VerifyAssertions.expected b/javascript/ql/test/ApiGraphs/custom-entry-point/VerifyAssertions.expected similarity index 100% rename from javascript/ql/test/library-tests/ApiGraphs/custom-entry-point/VerifyAssertions.expected rename to javascript/ql/test/ApiGraphs/custom-entry-point/VerifyAssertions.expected diff --git a/javascript/ql/test/ApiGraphs/custom-entry-point/VerifyAssertions.ql b/javascript/ql/test/ApiGraphs/custom-entry-point/VerifyAssertions.ql new file mode 100644 index 000000000000..fb8a943f2cad --- /dev/null +++ b/javascript/ql/test/ApiGraphs/custom-entry-point/VerifyAssertions.ql @@ -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 diff --git a/javascript/ql/test/library-tests/ApiGraphs/custom-entry-point/index.js b/javascript/ql/test/ApiGraphs/custom-entry-point/index.js similarity index 100% rename from javascript/ql/test/library-tests/ApiGraphs/custom-entry-point/index.js rename to javascript/ql/test/ApiGraphs/custom-entry-point/index.js diff --git a/javascript/ql/test/library-tests/ApiGraphs/custom-entry-point/package.json b/javascript/ql/test/ApiGraphs/custom-entry-point/package.json similarity index 100% rename from javascript/ql/test/library-tests/ApiGraphs/custom-entry-point/package.json rename to javascript/ql/test/ApiGraphs/custom-entry-point/package.json diff --git a/javascript/ql/test/library-tests/ApiGraphs/cyclic/VerifyAssertions.expected b/javascript/ql/test/ApiGraphs/cyclic/VerifyAssertions.expected similarity index 100% rename from javascript/ql/test/library-tests/ApiGraphs/cyclic/VerifyAssertions.expected rename to javascript/ql/test/ApiGraphs/cyclic/VerifyAssertions.expected diff --git a/javascript/ql/test/ApiGraphs/cyclic/VerifyAssertions.ql b/javascript/ql/test/ApiGraphs/cyclic/VerifyAssertions.ql new file mode 100644 index 000000000000..b9c54e26072d --- /dev/null +++ b/javascript/ql/test/ApiGraphs/cyclic/VerifyAssertions.ql @@ -0,0 +1 @@ +import ApiGraphs.VerifyAssertions diff --git a/javascript/ql/test/library-tests/ApiGraphs/cyclic/index.js b/javascript/ql/test/ApiGraphs/cyclic/index.js similarity index 100% rename from javascript/ql/test/library-tests/ApiGraphs/cyclic/index.js rename to javascript/ql/test/ApiGraphs/cyclic/index.js diff --git a/javascript/ql/test/library-tests/ApiGraphs/cyclic/package.json b/javascript/ql/test/ApiGraphs/cyclic/package.json similarity index 100% rename from javascript/ql/test/library-tests/ApiGraphs/cyclic/package.json rename to javascript/ql/test/ApiGraphs/cyclic/package.json diff --git a/javascript/ql/test/library-tests/ApiGraphs/dynamic-prop-read/VerifyAssertions.expected b/javascript/ql/test/ApiGraphs/dynamic-prop-read/VerifyAssertions.expected similarity index 100% rename from javascript/ql/test/library-tests/ApiGraphs/dynamic-prop-read/VerifyAssertions.expected rename to javascript/ql/test/ApiGraphs/dynamic-prop-read/VerifyAssertions.expected diff --git a/javascript/ql/test/ApiGraphs/dynamic-prop-read/VerifyAssertions.ql b/javascript/ql/test/ApiGraphs/dynamic-prop-read/VerifyAssertions.ql new file mode 100644 index 000000000000..b9c54e26072d --- /dev/null +++ b/javascript/ql/test/ApiGraphs/dynamic-prop-read/VerifyAssertions.ql @@ -0,0 +1 @@ +import ApiGraphs.VerifyAssertions diff --git a/javascript/ql/test/library-tests/ApiGraphs/dynamic-prop-read/index.js b/javascript/ql/test/ApiGraphs/dynamic-prop-read/index.js similarity index 100% rename from javascript/ql/test/library-tests/ApiGraphs/dynamic-prop-read/index.js rename to javascript/ql/test/ApiGraphs/dynamic-prop-read/index.js diff --git a/javascript/ql/test/library-tests/ApiGraphs/dynamic-prop-read/package.json b/javascript/ql/test/ApiGraphs/dynamic-prop-read/package.json similarity index 100% rename from javascript/ql/test/library-tests/ApiGraphs/dynamic-prop-read/package.json rename to javascript/ql/test/ApiGraphs/dynamic-prop-read/package.json diff --git a/javascript/ql/test/library-tests/ApiGraphs/imprecise-export/VerifyAssertions.expected b/javascript/ql/test/ApiGraphs/imprecise-export/VerifyAssertions.expected similarity index 100% rename from javascript/ql/test/library-tests/ApiGraphs/imprecise-export/VerifyAssertions.expected rename to javascript/ql/test/ApiGraphs/imprecise-export/VerifyAssertions.expected diff --git a/javascript/ql/test/ApiGraphs/imprecise-export/VerifyAssertions.ql b/javascript/ql/test/ApiGraphs/imprecise-export/VerifyAssertions.ql new file mode 100644 index 000000000000..b9c54e26072d --- /dev/null +++ b/javascript/ql/test/ApiGraphs/imprecise-export/VerifyAssertions.ql @@ -0,0 +1 @@ +import ApiGraphs.VerifyAssertions diff --git a/javascript/ql/test/library-tests/ApiGraphs/imprecise-export/index.js b/javascript/ql/test/ApiGraphs/imprecise-export/index.js similarity index 100% rename from javascript/ql/test/library-tests/ApiGraphs/imprecise-export/index.js rename to javascript/ql/test/ApiGraphs/imprecise-export/index.js diff --git a/javascript/ql/test/library-tests/ApiGraphs/imprecise-export/package.json b/javascript/ql/test/ApiGraphs/imprecise-export/package.json similarity index 100% rename from javascript/ql/test/library-tests/ApiGraphs/imprecise-export/package.json rename to javascript/ql/test/ApiGraphs/imprecise-export/package.json diff --git a/javascript/ql/test/library-tests/ApiGraphs/imprecision/VerifyAssertions.expected b/javascript/ql/test/ApiGraphs/imprecision/VerifyAssertions.expected similarity index 100% rename from javascript/ql/test/library-tests/ApiGraphs/imprecision/VerifyAssertions.expected rename to javascript/ql/test/ApiGraphs/imprecision/VerifyAssertions.expected diff --git a/javascript/ql/test/ApiGraphs/imprecision/VerifyAssertions.ql b/javascript/ql/test/ApiGraphs/imprecision/VerifyAssertions.ql new file mode 100644 index 000000000000..b9c54e26072d --- /dev/null +++ b/javascript/ql/test/ApiGraphs/imprecision/VerifyAssertions.ql @@ -0,0 +1 @@ +import ApiGraphs.VerifyAssertions diff --git a/javascript/ql/test/library-tests/ApiGraphs/imprecision/index.js b/javascript/ql/test/ApiGraphs/imprecision/index.js similarity index 100% rename from javascript/ql/test/library-tests/ApiGraphs/imprecision/index.js rename to javascript/ql/test/ApiGraphs/imprecision/index.js diff --git a/javascript/ql/test/library-tests/ApiGraphs/imprecision/package.json b/javascript/ql/test/ApiGraphs/imprecision/package.json similarity index 100% rename from javascript/ql/test/library-tests/ApiGraphs/imprecision/package.json rename to javascript/ql/test/ApiGraphs/imprecision/package.json diff --git a/javascript/ql/test/library-tests/ApiGraphs/namespaced-package/VerifyAssertions.expected b/javascript/ql/test/ApiGraphs/namespaced-package/VerifyAssertions.expected similarity index 100% rename from javascript/ql/test/library-tests/ApiGraphs/namespaced-package/VerifyAssertions.expected rename to javascript/ql/test/ApiGraphs/namespaced-package/VerifyAssertions.expected diff --git a/javascript/ql/test/ApiGraphs/namespaced-package/VerifyAssertions.ql b/javascript/ql/test/ApiGraphs/namespaced-package/VerifyAssertions.ql new file mode 100644 index 000000000000..b9c54e26072d --- /dev/null +++ b/javascript/ql/test/ApiGraphs/namespaced-package/VerifyAssertions.ql @@ -0,0 +1 @@ +import ApiGraphs.VerifyAssertions diff --git a/javascript/ql/test/library-tests/ApiGraphs/namespaced-package/index.js b/javascript/ql/test/ApiGraphs/namespaced-package/index.js similarity index 100% rename from javascript/ql/test/library-tests/ApiGraphs/namespaced-package/index.js rename to javascript/ql/test/ApiGraphs/namespaced-package/index.js diff --git a/javascript/ql/test/library-tests/ApiGraphs/namespaced-package/package.json b/javascript/ql/test/ApiGraphs/namespaced-package/package.json similarity index 100% rename from javascript/ql/test/library-tests/ApiGraphs/namespaced-package/package.json rename to javascript/ql/test/ApiGraphs/namespaced-package/package.json diff --git a/javascript/ql/test/library-tests/ApiGraphs/nested-property-export/VerifyAssertions.expected b/javascript/ql/test/ApiGraphs/nested-property-export/VerifyAssertions.expected similarity index 100% rename from javascript/ql/test/library-tests/ApiGraphs/nested-property-export/VerifyAssertions.expected rename to javascript/ql/test/ApiGraphs/nested-property-export/VerifyAssertions.expected diff --git a/javascript/ql/test/ApiGraphs/nested-property-export/VerifyAssertions.ql b/javascript/ql/test/ApiGraphs/nested-property-export/VerifyAssertions.ql new file mode 100644 index 000000000000..b9c54e26072d --- /dev/null +++ b/javascript/ql/test/ApiGraphs/nested-property-export/VerifyAssertions.ql @@ -0,0 +1 @@ +import ApiGraphs.VerifyAssertions diff --git a/javascript/ql/test/library-tests/ApiGraphs/nested-property-export/index.js b/javascript/ql/test/ApiGraphs/nested-property-export/index.js similarity index 100% rename from javascript/ql/test/library-tests/ApiGraphs/nested-property-export/index.js rename to javascript/ql/test/ApiGraphs/nested-property-export/index.js diff --git a/javascript/ql/test/library-tests/ApiGraphs/nested-property-export/package.json b/javascript/ql/test/ApiGraphs/nested-property-export/package.json similarity index 100% rename from javascript/ql/test/library-tests/ApiGraphs/nested-property-export/package.json rename to javascript/ql/test/ApiGraphs/nested-property-export/package.json diff --git a/javascript/ql/test/library-tests/ApiGraphs/nonlocal/VerifyAssertions.expected b/javascript/ql/test/ApiGraphs/nonlocal/VerifyAssertions.expected similarity index 100% rename from javascript/ql/test/library-tests/ApiGraphs/nonlocal/VerifyAssertions.expected rename to javascript/ql/test/ApiGraphs/nonlocal/VerifyAssertions.expected diff --git a/javascript/ql/test/ApiGraphs/nonlocal/VerifyAssertions.ql b/javascript/ql/test/ApiGraphs/nonlocal/VerifyAssertions.ql new file mode 100644 index 000000000000..b9c54e26072d --- /dev/null +++ b/javascript/ql/test/ApiGraphs/nonlocal/VerifyAssertions.ql @@ -0,0 +1 @@ +import ApiGraphs.VerifyAssertions diff --git a/javascript/ql/test/library-tests/ApiGraphs/nonlocal/index.js b/javascript/ql/test/ApiGraphs/nonlocal/index.js similarity index 100% rename from javascript/ql/test/library-tests/ApiGraphs/nonlocal/index.js rename to javascript/ql/test/ApiGraphs/nonlocal/index.js diff --git a/javascript/ql/test/library-tests/ApiGraphs/nonlocal/package.json b/javascript/ql/test/ApiGraphs/nonlocal/package.json similarity index 100% rename from javascript/ql/test/library-tests/ApiGraphs/nonlocal/package.json rename to javascript/ql/test/ApiGraphs/nonlocal/package.json diff --git a/javascript/ql/test/library-tests/ApiGraphs/promises/VerifyAssertions.expected b/javascript/ql/test/ApiGraphs/promises/VerifyAssertions.expected similarity index 100% rename from javascript/ql/test/library-tests/ApiGraphs/promises/VerifyAssertions.expected rename to javascript/ql/test/ApiGraphs/promises/VerifyAssertions.expected diff --git a/javascript/ql/test/ApiGraphs/promises/VerifyAssertions.ql b/javascript/ql/test/ApiGraphs/promises/VerifyAssertions.ql new file mode 100644 index 000000000000..b9c54e26072d --- /dev/null +++ b/javascript/ql/test/ApiGraphs/promises/VerifyAssertions.ql @@ -0,0 +1 @@ +import ApiGraphs.VerifyAssertions diff --git a/javascript/ql/test/library-tests/ApiGraphs/promises/index.js b/javascript/ql/test/ApiGraphs/promises/index.js similarity index 100% rename from javascript/ql/test/library-tests/ApiGraphs/promises/index.js rename to javascript/ql/test/ApiGraphs/promises/index.js diff --git a/javascript/ql/test/library-tests/ApiGraphs/promises/package.json b/javascript/ql/test/ApiGraphs/promises/package.json similarity index 100% rename from javascript/ql/test/library-tests/ApiGraphs/promises/package.json rename to javascript/ql/test/ApiGraphs/promises/package.json diff --git a/javascript/ql/test/library-tests/ApiGraphs/reexport/VerifyAssertions.expected b/javascript/ql/test/ApiGraphs/reexport/VerifyAssertions.expected similarity index 100% rename from javascript/ql/test/library-tests/ApiGraphs/reexport/VerifyAssertions.expected rename to javascript/ql/test/ApiGraphs/reexport/VerifyAssertions.expected diff --git a/javascript/ql/test/ApiGraphs/reexport/VerifyAssertions.ql b/javascript/ql/test/ApiGraphs/reexport/VerifyAssertions.ql new file mode 100644 index 000000000000..b9c54e26072d --- /dev/null +++ b/javascript/ql/test/ApiGraphs/reexport/VerifyAssertions.ql @@ -0,0 +1 @@ +import ApiGraphs.VerifyAssertions diff --git a/javascript/ql/test/library-tests/ApiGraphs/reexport/index.js b/javascript/ql/test/ApiGraphs/reexport/index.js similarity index 100% rename from javascript/ql/test/library-tests/ApiGraphs/reexport/index.js rename to javascript/ql/test/ApiGraphs/reexport/index.js diff --git a/javascript/ql/test/library-tests/ApiGraphs/reexport/lib/impl.js b/javascript/ql/test/ApiGraphs/reexport/lib/impl.js similarity index 100% rename from javascript/ql/test/library-tests/ApiGraphs/reexport/lib/impl.js rename to javascript/ql/test/ApiGraphs/reexport/lib/impl.js diff --git a/javascript/ql/test/library-tests/ApiGraphs/reexport/lib/utils.js b/javascript/ql/test/ApiGraphs/reexport/lib/utils.js similarity index 100% rename from javascript/ql/test/library-tests/ApiGraphs/reexport/lib/utils.js rename to javascript/ql/test/ApiGraphs/reexport/lib/utils.js diff --git a/javascript/ql/test/library-tests/ApiGraphs/reexport/package.json b/javascript/ql/test/ApiGraphs/reexport/package.json similarity index 100% rename from javascript/ql/test/library-tests/ApiGraphs/reexport/package.json rename to javascript/ql/test/ApiGraphs/reexport/package.json diff --git a/javascript/ql/test/library-tests/ApiGraphs/return-self/VerifyAssertions.expected b/javascript/ql/test/ApiGraphs/return-self/VerifyAssertions.expected similarity index 100% rename from javascript/ql/test/library-tests/ApiGraphs/return-self/VerifyAssertions.expected rename to javascript/ql/test/ApiGraphs/return-self/VerifyAssertions.expected diff --git a/javascript/ql/test/ApiGraphs/return-self/VerifyAssertions.ql b/javascript/ql/test/ApiGraphs/return-self/VerifyAssertions.ql new file mode 100644 index 000000000000..b9c54e26072d --- /dev/null +++ b/javascript/ql/test/ApiGraphs/return-self/VerifyAssertions.ql @@ -0,0 +1 @@ +import ApiGraphs.VerifyAssertions diff --git a/javascript/ql/test/library-tests/ApiGraphs/return-self/index.js b/javascript/ql/test/ApiGraphs/return-self/index.js similarity index 100% rename from javascript/ql/test/library-tests/ApiGraphs/return-self/index.js rename to javascript/ql/test/ApiGraphs/return-self/index.js diff --git a/javascript/ql/test/library-tests/ApiGraphs/return-self/package.json b/javascript/ql/test/ApiGraphs/return-self/package.json similarity index 100% rename from javascript/ql/test/library-tests/ApiGraphs/return-self/package.json rename to javascript/ql/test/ApiGraphs/return-self/package.json diff --git a/javascript/ql/test/library-tests/ApiGraphs/typed/VerifyAssertions.expected b/javascript/ql/test/ApiGraphs/typed/VerifyAssertions.expected similarity index 100% rename from javascript/ql/test/library-tests/ApiGraphs/typed/VerifyAssertions.expected rename to javascript/ql/test/ApiGraphs/typed/VerifyAssertions.expected diff --git a/javascript/ql/test/ApiGraphs/typed/VerifyAssertions.ql b/javascript/ql/test/ApiGraphs/typed/VerifyAssertions.ql new file mode 100644 index 000000000000..b9c54e26072d --- /dev/null +++ b/javascript/ql/test/ApiGraphs/typed/VerifyAssertions.ql @@ -0,0 +1 @@ +import ApiGraphs.VerifyAssertions diff --git a/javascript/ql/test/library-tests/ApiGraphs/typed/index.ts b/javascript/ql/test/ApiGraphs/typed/index.ts similarity index 100% rename from javascript/ql/test/library-tests/ApiGraphs/typed/index.ts rename to javascript/ql/test/ApiGraphs/typed/index.ts diff --git a/javascript/ql/test/library-tests/ApiGraphs/typed/package.json b/javascript/ql/test/ApiGraphs/typed/package.json similarity index 100% rename from javascript/ql/test/library-tests/ApiGraphs/typed/package.json rename to javascript/ql/test/ApiGraphs/typed/package.json diff --git a/javascript/ql/test/library-tests/ApiGraphs/typed/shim.d.ts b/javascript/ql/test/ApiGraphs/typed/shim.d.ts similarity index 100% rename from javascript/ql/test/library-tests/ApiGraphs/typed/shim.d.ts rename to javascript/ql/test/ApiGraphs/typed/shim.d.ts diff --git a/javascript/ql/test/library-tests/ApiGraphs/typed/tsconfig.json b/javascript/ql/test/ApiGraphs/typed/tsconfig.json similarity index 100% rename from javascript/ql/test/library-tests/ApiGraphs/typed/tsconfig.json rename to javascript/ql/test/ApiGraphs/typed/tsconfig.json diff --git a/javascript/ql/test/library-tests/ApiGraphs/VerifyAssertions.expected b/javascript/ql/test/library-tests/ApiGraphs/VerifyAssertions.expected deleted file mode 100644 index 6b9dcbfec500..000000000000 --- a/javascript/ql/test/library-tests/ApiGraphs/VerifyAssertions.expected +++ /dev/null @@ -1,4 +0,0 @@ -| reexport/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. | -| typed/index.ts:14:36:14:106 | /* use ... )))) */ | use (module mongodb) has no outgoing edge labelled member Collection; it does have outgoing edges labelled member exports. | -| typed/index.ts:22:39:22:119 | /* def ... )))) */ | use (module mongoose) has no outgoing edge labelled member Model; it does have outgoing edges labelled member exports. | -| typed/index.ts:23:39:23:119 | /* def ... )))) */ | use (module mongoose) has no outgoing edge labelled member Query; it does have outgoing edges labelled member exports. | diff --git a/javascript/ql/test/library-tests/ApiGraphs/argprops/VerifyAssertions.qlref b/javascript/ql/test/library-tests/ApiGraphs/argprops/VerifyAssertions.qlref deleted file mode 100644 index 47f26fcc174e..000000000000 --- a/javascript/ql/test/library-tests/ApiGraphs/argprops/VerifyAssertions.qlref +++ /dev/null @@ -1 +0,0 @@ -library-tests/ApiGraphs/VerifyAssertions.ql diff --git a/javascript/ql/test/library-tests/ApiGraphs/async-await/VerifyAssertions.qlref b/javascript/ql/test/library-tests/ApiGraphs/async-await/VerifyAssertions.qlref deleted file mode 100644 index 47f26fcc174e..000000000000 --- a/javascript/ql/test/library-tests/ApiGraphs/async-await/VerifyAssertions.qlref +++ /dev/null @@ -1 +0,0 @@ -library-tests/ApiGraphs/VerifyAssertions.ql diff --git a/javascript/ql/test/library-tests/ApiGraphs/branching-flow/VerifyAssertions.qlref b/javascript/ql/test/library-tests/ApiGraphs/branching-flow/VerifyAssertions.qlref deleted file mode 100644 index 47f26fcc174e..000000000000 --- a/javascript/ql/test/library-tests/ApiGraphs/branching-flow/VerifyAssertions.qlref +++ /dev/null @@ -1 +0,0 @@ -library-tests/ApiGraphs/VerifyAssertions.ql diff --git a/javascript/ql/test/library-tests/ApiGraphs/classes/VerifyAssertions.qlref b/javascript/ql/test/library-tests/ApiGraphs/classes/VerifyAssertions.qlref deleted file mode 100644 index 47f26fcc174e..000000000000 --- a/javascript/ql/test/library-tests/ApiGraphs/classes/VerifyAssertions.qlref +++ /dev/null @@ -1 +0,0 @@ -library-tests/ApiGraphs/VerifyAssertions.ql diff --git a/javascript/ql/test/library-tests/ApiGraphs/ctor-arg/VerifyAssertions.qlref b/javascript/ql/test/library-tests/ApiGraphs/ctor-arg/VerifyAssertions.qlref deleted file mode 100644 index 47f26fcc174e..000000000000 --- a/javascript/ql/test/library-tests/ApiGraphs/ctor-arg/VerifyAssertions.qlref +++ /dev/null @@ -1 +0,0 @@ -library-tests/ApiGraphs/VerifyAssertions.ql diff --git a/javascript/ql/test/library-tests/ApiGraphs/custom-entry-point/VerifyAssertions.qlref b/javascript/ql/test/library-tests/ApiGraphs/custom-entry-point/VerifyAssertions.qlref deleted file mode 100644 index 47f26fcc174e..000000000000 --- a/javascript/ql/test/library-tests/ApiGraphs/custom-entry-point/VerifyAssertions.qlref +++ /dev/null @@ -1 +0,0 @@ -library-tests/ApiGraphs/VerifyAssertions.ql diff --git a/javascript/ql/test/library-tests/ApiGraphs/cyclic/VerifyAssertions.qlref b/javascript/ql/test/library-tests/ApiGraphs/cyclic/VerifyAssertions.qlref deleted file mode 100644 index 47f26fcc174e..000000000000 --- a/javascript/ql/test/library-tests/ApiGraphs/cyclic/VerifyAssertions.qlref +++ /dev/null @@ -1 +0,0 @@ -library-tests/ApiGraphs/VerifyAssertions.ql diff --git a/javascript/ql/test/library-tests/ApiGraphs/dynamic-prop-read/VerifyAssertions.qlref b/javascript/ql/test/library-tests/ApiGraphs/dynamic-prop-read/VerifyAssertions.qlref deleted file mode 100644 index 47f26fcc174e..000000000000 --- a/javascript/ql/test/library-tests/ApiGraphs/dynamic-prop-read/VerifyAssertions.qlref +++ /dev/null @@ -1 +0,0 @@ -library-tests/ApiGraphs/VerifyAssertions.ql diff --git a/javascript/ql/test/library-tests/ApiGraphs/imprecise-export/VerifyAssertions.qlref b/javascript/ql/test/library-tests/ApiGraphs/imprecise-export/VerifyAssertions.qlref deleted file mode 100644 index 47f26fcc174e..000000000000 --- a/javascript/ql/test/library-tests/ApiGraphs/imprecise-export/VerifyAssertions.qlref +++ /dev/null @@ -1 +0,0 @@ -library-tests/ApiGraphs/VerifyAssertions.ql diff --git a/javascript/ql/test/library-tests/ApiGraphs/imprecision/VerifyAssertions.qlref b/javascript/ql/test/library-tests/ApiGraphs/imprecision/VerifyAssertions.qlref deleted file mode 100644 index 47f26fcc174e..000000000000 --- a/javascript/ql/test/library-tests/ApiGraphs/imprecision/VerifyAssertions.qlref +++ /dev/null @@ -1 +0,0 @@ -library-tests/ApiGraphs/VerifyAssertions.ql diff --git a/javascript/ql/test/library-tests/ApiGraphs/namespaced-package/VerifyAssertions.qlref b/javascript/ql/test/library-tests/ApiGraphs/namespaced-package/VerifyAssertions.qlref deleted file mode 100644 index 47f26fcc174e..000000000000 --- a/javascript/ql/test/library-tests/ApiGraphs/namespaced-package/VerifyAssertions.qlref +++ /dev/null @@ -1 +0,0 @@ -library-tests/ApiGraphs/VerifyAssertions.ql diff --git a/javascript/ql/test/library-tests/ApiGraphs/nested-property-export/VerifyAssertions.qlref b/javascript/ql/test/library-tests/ApiGraphs/nested-property-export/VerifyAssertions.qlref deleted file mode 100644 index 47f26fcc174e..000000000000 --- a/javascript/ql/test/library-tests/ApiGraphs/nested-property-export/VerifyAssertions.qlref +++ /dev/null @@ -1 +0,0 @@ -library-tests/ApiGraphs/VerifyAssertions.ql diff --git a/javascript/ql/test/library-tests/ApiGraphs/nonlocal/VerifyAssertions.qlref b/javascript/ql/test/library-tests/ApiGraphs/nonlocal/VerifyAssertions.qlref deleted file mode 100644 index 47f26fcc174e..000000000000 --- a/javascript/ql/test/library-tests/ApiGraphs/nonlocal/VerifyAssertions.qlref +++ /dev/null @@ -1 +0,0 @@ -library-tests/ApiGraphs/VerifyAssertions.ql diff --git a/javascript/ql/test/library-tests/ApiGraphs/promises/VerifyAssertions.qlref b/javascript/ql/test/library-tests/ApiGraphs/promises/VerifyAssertions.qlref deleted file mode 100644 index 47f26fcc174e..000000000000 --- a/javascript/ql/test/library-tests/ApiGraphs/promises/VerifyAssertions.qlref +++ /dev/null @@ -1 +0,0 @@ -library-tests/ApiGraphs/VerifyAssertions.ql diff --git a/javascript/ql/test/library-tests/ApiGraphs/reexport/VerifyAssertions.qlref b/javascript/ql/test/library-tests/ApiGraphs/reexport/VerifyAssertions.qlref deleted file mode 100644 index 47f26fcc174e..000000000000 --- a/javascript/ql/test/library-tests/ApiGraphs/reexport/VerifyAssertions.qlref +++ /dev/null @@ -1 +0,0 @@ -library-tests/ApiGraphs/VerifyAssertions.ql diff --git a/javascript/ql/test/library-tests/ApiGraphs/return-self/VerifyAssertions.qlref b/javascript/ql/test/library-tests/ApiGraphs/return-self/VerifyAssertions.qlref deleted file mode 100644 index 47f26fcc174e..000000000000 --- a/javascript/ql/test/library-tests/ApiGraphs/return-self/VerifyAssertions.qlref +++ /dev/null @@ -1 +0,0 @@ -library-tests/ApiGraphs/VerifyAssertions.ql diff --git a/javascript/ql/test/library-tests/ApiGraphs/typed/VerifyAssertions.qlref b/javascript/ql/test/library-tests/ApiGraphs/typed/VerifyAssertions.qlref deleted file mode 100644 index 47f26fcc174e..000000000000 --- a/javascript/ql/test/library-tests/ApiGraphs/typed/VerifyAssertions.qlref +++ /dev/null @@ -1 +0,0 @@ -library-tests/ApiGraphs/VerifyAssertions.ql From cfc91cc5f127880090ad6533a65bc8b8e10cc290 Mon Sep 17 00:00:00 2001 From: Max Schaefer Date: Fri, 4 Sep 2020 10:57:21 +0100 Subject: [PATCH 23/26] JavaScript: Drop "feature" terminology. It turned out to be more confusing than helpful, so we're back with plain old API-graph "nodes". --- .../ql/src/semmle/javascript/ApiGraphs.qll | 200 +++++++++--------- .../src/semmle/javascript/frameworks/SQL.qll | 38 ++-- .../frameworks/SystemCommandExecutors.qll | 4 +- .../security/dataflow/MissingRateLimiting.qll | 2 +- .../ql/test/ApiGraphs/VerifyAssertions.qll | 8 +- 5 files changed, 131 insertions(+), 121 deletions(-) diff --git a/javascript/ql/src/semmle/javascript/ApiGraphs.qll b/javascript/ql/src/semmle/javascript/ApiGraphs.qll index 314dbf0c605b..344d883938ac 100644 --- a/javascript/ql/src/semmle/javascript/ApiGraphs.qll +++ b/javascript/ql/src/semmle/javascript/ApiGraphs.qll @@ -1,12 +1,12 @@ /** * Provides an implementation of _API graphs_, which are an abstract representation of the API - * surface of an NPM package. + * surface used and/or defined by a code base. * - * The nodes of the API graph are called _features_, with labeled edges representing how features - * relate to each other. For example, if one of the API features represents a function, then there - * will be features corresponding to the function's parameters, which are connected to the function - * feature by edges labeled `parameter `. There are special _points-to_ edges labeled with the - * empty string which express the fact that one feature is an alias for another. + * 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 `. */ import javascript @@ -16,18 +16,18 @@ import javascript */ module API { /** - * An abstract representation of an API feature such as a function exported by an npm package, - * a parameter of such a function, or its result. + * 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 Feature extends Impl::TFeature { + class Node extends Impl::TNode { /** - * Gets a data-flow node corresponding to a use of this API feature. + * 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 feature `readFileSync` from the + * 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 feature corresponding to the first parameter of `plusOne`. + * `x` are uses of the first parameter of `plusOne`. */ DataFlow::Node getAUse() { exists(DataFlow::SourceNode src | Impl::use(this, src) | @@ -36,11 +36,11 @@ module API { } /** - * Gets a data-flow node corresponding to the right-hand side of a definition of this API - * feature. + * 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 feature `plusOne` of + * `(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. * @@ -52,39 +52,48 @@ module API { DataFlow::Node getARhs() { Impl::rhs(this, result) } /** - * Gets a feature representing member `m` of this one. + * 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] - Feature getMember(string m) { result = getASuccessor(Label::member(m)) } + Node getMember(string m) { result = getASuccessor(Label::member(m)) } /** - * Gets a feature representing a member with a computed name of this one. + * Gets a node representing a member of this API component where the name of the member is + * not known statically. */ - Feature getUnknownMember() { result = getASuccessor(Label::unknownMember()) } + Node getUnknownMember() { result = getASuccessor(Label::unknownMember()) } /** - * Gets a feature representing a member of this one. + * Gets a node representing a member of this API component where the name of the member may + * or may not be known statically. */ - Feature getAMember() { + Node getAMember() { result = getASuccessor(Label::member(_)) or result = getUnknownMember() } /** - * Gets a feature representing an instance of this one, that is, an object whose - * constructor is this feature. + * 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. */ - Feature getInstance() { result = getASuccessor(Label::instance()) } + Node getInstance() { result = getASuccessor(Label::instance()) } /** - * Gets a feature representing the `i`th parameter of this one. + * Gets a node representing the `i`th parameter of the function represented by this node. */ bindingset[i] - Feature getParameter(int i) { result = getASuccessor(Label::parameter(i)) } + Node getParameter(int i) { result = getASuccessor(Label::parameter(i)) } /** - * Gets the number of parameters of this feature. + * Gets the number of parameters of the function represented by this node. */ int getNumParameter() { result = @@ -92,73 +101,75 @@ module API { } /** - * Gets a feature representing the last parameter of this one. + * Gets a node representing the last parameter of the function represented by this node. */ - Feature getLastParameter() { result = getParameter(getNumParameter() - 1) } + Node getLastParameter() { result = getParameter(getNumParameter() - 1) } /** - * Gets a feature representing the receiver of this one. + * Gets a node representing the receiver of the function represented by this node. */ - Feature getReceiver() { result = getASuccessor(Label::receiver()) } + Node getReceiver() { result = getASuccessor(Label::receiver()) } /** - * Gets a feature representing a parameter or the receiver of this one. + * Gets a node representing a parameter or the receiver of the function represented by this + * node. */ - Feature getAParameter() { + Node getAParameter() { result = getASuccessor(Label::parameterByStringIndex(_)) or result = getReceiver() } /** - * Gets a feature representing the result of this one. + * Gets a node representing the result of the function represented by this node. */ - Feature getReturn() { result = getASuccessor(Label::return()) } + Node getReturn() { result = getASuccessor(Label::return()) } /** - * Gets a feature representing the promised value wrapped in this promise. + * Gets a node representing the promised value wrapped in the `Promise` object represented by + * this node. */ - Feature getPromised() { result = getASuccessor(Label::promised()) } + Node getPromised() { result = getASuccessor(Label::promised()) } /** * Gets a string representation of the lexicographically least among all shortest access paths - * from the root to this feature. + * from the root to this node. */ string getPath() { result = min(string p | p = getAPath(Impl::distanceFromRoot(this)) | p) } /** - * Gets a feature such that there is an edge in the API graph between this feature and the other - * one, and that edge is labeled with `lbl`. This predicate skips through points-to edges. + * 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`. */ - Feature getASuccessor(string lbl) { Impl::edge(this, lbl, result) } + Node getASuccessor(string lbl) { Impl::edge(this, lbl, result) } /** - * Gets a feature such that there is an edge in the API graph between that other feature and - * this one, and that edge is labeled with `lbl`. This predicate skips through points-to edges. + * 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` */ - Feature getAPredecessor(string lbl) { this = result.getASuccessor(lbl) } + Node getAPredecessor(string lbl) { this = result.getASuccessor(lbl) } /** - * Gets a feature such that there is an edge in the API graph between this feature and the other - * one, possibly skipping through points-to edges. + * Gets a node such that there is an edge in the API graph between this node and the other + * one. */ - Feature getAPredecessor() { result = getAPredecessor(_) } + Node getAPredecessor() { result = getAPredecessor(_) } /** - * Gets a feature such that there is an edge in the API graph between that other feature and - * this one, possibly skipping through points-to edges. + * Gets a node such that there is an edge in the API graph between that other node and + * this one. */ - Feature getASuccessor() { result = getASuccessor(_) } + Node getASuccessor() { result = getASuccessor(_) } /** - * Holds if this feature may take its value from `that` feature. + * 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 feature. + * definition of this node. */ - predicate refersTo(Feature that) { this.getARhs() = that.getAUse() } + predicate refersTo(Node that) { this.getARhs() = that.getAUse() } /** - * Gets the unique data-flow that gives rise to this feature, if any. + * Gets the unique data-flow that gives rise to this node, if any. */ private DataFlow::Node getRepresentativeNode() { this = Impl::MkClassInstance(result) or @@ -168,10 +179,10 @@ module API { } /** - * Holds if this feature is located in file `path` between line `startline`, column `startcol`, + * Holds if this node is located in file `path` between line `startline`, column `startcol`, * and line `endline`, column `endcol`. * - * For features that do not have a meaningful location, `path` is the empty string and all other + * 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) { @@ -186,21 +197,21 @@ module API { } /** - * Gets a textual representation of this feature. + * 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 feature. + * 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(Feature pred, string lbl, string predpath | + exists(Node pred, string lbl, string predpath | Impl::edge(pred, lbl, this) and lbl != "" and predpath = pred.getAPath(length - 1) and @@ -214,32 +225,32 @@ module API { } } - /** The root feature of an API graph. */ - class Root extends Feature, Impl::MkRoot { + /** The root node of an API graph. */ + class Root extends Node, Impl::MkRoot { override string toString() { result = "root" } } - /** A feature corresponding to a definition of an API component. */ - class Definition extends Feature, Impl::TDef { + /** A node corresponding to a definition of an API component. */ + class Definition extends Node, Impl::TDef { override string toString() { result = "def " + getPath() } } - /** A feature corresponding to the use of an API component. */ - class Use extends Feature, Impl::TUse { + /** 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 feature. */ + /** Gets the root node. */ Root root() { any() } - /** Gets a feature corresponding to an import of module `m`. */ - Feature moduleImport(string m) { + /** Gets a node corresponding to an import of module `m`. */ + Node moduleImport(string m) { result = Impl::MkModuleImport(m) or - result = Impl::MkModuleImport(m).(Feature).getMember("default") + result = Impl::MkModuleImport(m).(Node).getMember("default") } - /** Gets a feature corresponding to an export of module `m`. */ - Feature moduleExport(string m) { result = Impl::MkModuleDef(m).(Feature).getMember("exports") } + /** 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. @@ -261,30 +272,29 @@ module API { /** * Provides the actual implementation of API graphs, cached for performance. * - * Ideally, we'd like features to correspond to (global) access paths, with edge labels - * corresponding to extending the access path by one element, or (in the case of points-to - * edges) recording alias information. We also want to be able to map features to their - * definitions and uses in the data-flow graph, and this should happen modulo + * 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. However, expressing + * 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 feature in the API graph" without negative recursion is tricky. + * this node in the API graph" without negative recursion is tricky. * - * So instead most features are directly associated with a data-flow node, representing - * either a use or a definition of the feature. This ensures that we only have a finite - * number of features. However, we can now have multiple features with the same access + * 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 feature can have multiple access paths (which is, of + * 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 TFeature = + newtype TNode = MkRoot() or MkModuleDef(string m) { exists(MkModuleExport(m)) } or MkModuleUse(string m) { exists(MkModuleImport(m)) } or @@ -355,11 +365,11 @@ module API { } /** - * Holds if `rhs` is the right-hand side of a definition of a feature that should have an + * 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(TFeature base, string lbl, DataFlow::Node rhs) { + predicate rhs(TNode base, string lbl, DataFlow::Node rhs) { hasSemantics(rhs) and ( base = MkRoot() and @@ -422,10 +432,10 @@ module API { } /** - * Holds if `rhs` is the right-hand side of a definition of feature `nd`. + * Holds if `rhs` is the right-hand side of a definition of node `nd`. */ cached - predicate rhs(TFeature nd, DataFlow::Node rhs) { + predicate rhs(TNode nd, DataFlow::Node rhs) { exists(string m | nd = MkModuleExport(m) | exports(m, rhs)) or nd = MkDef(rhs) @@ -437,11 +447,11 @@ module API { } /** - * Holds if `ref` is a use of a feature that should have an incoming edge from `base` labeled + * 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(TFeature base, string lbl, DataFlow::Node ref) { + predicate use(TNode base, string lbl, DataFlow::Node ref) { hasSemantics(ref) and ( base = MkRoot() and @@ -494,10 +504,10 @@ module API { } /** - * Holds if `ref` is a use of feature `nd`. + * Holds if `ref` is a use of node `nd`. */ cached - predicate use(TFeature nd, DataFlow::Node ref) { + predicate use(TNode 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 @@ -562,7 +572,7 @@ module API { } /** - * Gets a node that is inter-procedurally reachable from `nd`, which is a use of some feature. + * Gets a node that is inter-procedurally reachable from `nd`, which is a use of some node. */ cached DataFlow::SourceNode trackUseNode(DataFlow::SourceNode nd) { @@ -578,7 +588,7 @@ module API { } /** - * Gets a node that inter-procedurally flows into `nd`, which is a definition of some feature. + * Gets a node that inter-procedurally flows into `nd`, which is a definition of some node. */ cached DataFlow::SourceNode trackDefNode(DataFlow::Node nd) { @@ -596,7 +606,7 @@ module API { * Holds if there is an edge from `pred` to `succ` in the API graph that is labeled with `lbl`. */ cached - predicate edge(TFeature pred, string lbl, TFeature succ) { + predicate edge(TNode pred, string lbl, TNode succ) { exists(string m | pred = MkRoot() and lbl = Label::mod(m) @@ -659,11 +669,11 @@ module API { /** * Holds if there is an edge from `pred` to `succ` in the API graph. */ - private predicate edge(TFeature pred, TFeature succ) { edge(pred, _, succ) } + private predicate edge(TNode pred, TNode succ) { edge(pred, _, succ) } /** Gets the shortest distance from the root to `nd` in the API graph. */ cached - int distanceFromRoot(TFeature nd) = shortestDistances(MkRoot/0, edge/2)(_, nd, result) + int distanceFromRoot(TNode nd) = shortestDistances(MkRoot/0, edge/2)(_, nd, result) } import Label as EdgeLabel diff --git a/javascript/ql/src/semmle/javascript/frameworks/SQL.qll b/javascript/ql/src/semmle/javascript/frameworks/SQL.qll index abba43b2eac4..19eaf168c085 100644 --- a/javascript/ql/src/semmle/javascript/frameworks/SQL.qll +++ b/javascript/ql/src/semmle/javascript/frameworks/SQL.qll @@ -29,16 +29,16 @@ module SQL { */ private module MySql { /** Gets the package name `mysql` or `mysql2`. */ - API::Feature mysql() { result = API::moduleImport(["mysql", "mysql2"]) } + API::Node mysql() { result = API::moduleImport(["mysql", "mysql2"]) } /** Gets a call to `mysql.createConnection`. */ - API::Feature createConnection() { result = mysql().getMember("createConnection").getReturn() } + API::Node createConnection() { result = mysql().getMember("createConnection").getReturn() } /** Gets a call to `mysql.createPool`. */ - API::Feature createPool() { result = mysql().getMember("createPool").getReturn() } + API::Node createPool() { result = mysql().getMember("createPool").getReturn() } /** Gets a data flow node that contains a freshly created MySQL connection instance. */ - API::Feature connection() { + API::Node connection() { result = createConnection() or result = createPool().getMember("getConnection").getParameter(0).getParameter(1) @@ -47,7 +47,7 @@ private module MySql { /** A call to the MySql `query` method. */ private class QueryCall extends DatabaseAccess, DataFlow::MethodCallNode { QueryCall() { - exists(API::Feature recv | recv = createPool() or recv = connection() | + exists(API::Node recv | recv = createPool() or recv = connection() | this = recv.getMember("query").getReturn().getAUse() ) } @@ -79,7 +79,7 @@ private module MySql { string kind; Credentials() { - exists(API::Feature call, string prop | + exists(API::Node call, string prop | call in [createConnection(), createPool()] and call.getAUse().asExpr().(CallExpr).hasOptionArgument(0, prop, this) and ( @@ -99,10 +99,10 @@ private module MySql { */ private module Postgres { /** Gets an expression of the form `new require('pg').Client()`. */ - API::Feature newClient() { result = API::moduleImport("pg").getMember("Client").getInstance() } + API::Node newClient() { result = API::moduleImport("pg").getMember("Client").getInstance() } /** Gets a data flow node that holds a freshly created Postgres client instance. */ - API::Feature client() { + API::Node client() { result = newClient() or // pool.connect(function(err, client) { ... }) @@ -110,7 +110,7 @@ private module Postgres { } /** Gets an expression that constructs a new connection pool. */ - API::Feature newPool() { + API::Node newPool() { // new require('pg').Pool() result = API::moduleImport("pg").getMember("Pool").getInstance() or @@ -155,14 +155,14 @@ private module Postgres { */ private module Sqlite { /** Gets a reference to the `sqlite3` module. */ - API::Feature sqlite() { + API::Node sqlite() { result = API::moduleImport("sqlite3") or result = sqlite().getMember("verbose").getReturn() } /** Gets an expression that constructs a Sqlite database instance. */ - API::Feature newDb() { + API::Node newDb() { // new require('sqlite3').Database() result = sqlite().getMember("Database").getInstance() } @@ -196,10 +196,10 @@ private module Sqlite { */ private module MsSql { /** Gets a reference to the `mssql` module. */ - API::Feature mssql() { result = API::moduleImport("mssql") } + API::Node mssql() { result = API::moduleImport("mssql") } /** Gets an expression that creates a request object. */ - API::Feature request() { + API::Node request() { // new require('mssql').Request() result = mssql().getMember("Request").getInstance() or @@ -274,10 +274,10 @@ private module MsSql { */ private module Sequelize { /** Gets an import of the `sequelize` module. */ - API::Feature sequelize() { result = API::moduleImport("sequelize") } + API::Node sequelize() { result = API::moduleImport("sequelize") } /** Gets an expression that creates an instance of the `Sequelize` class. */ - API::Feature newSequelize() { result = sequelize().getInstance() } + API::Node newSequelize() { result = sequelize().getInstance() } /** A call to `Sequelize.query`. */ private class QueryCall extends DatabaseAccess, DataFlow::MethodCallNode { @@ -327,7 +327,7 @@ private module Spanner { /** * Gets a node that refers to the `Spanner` class */ - API::Feature spanner() { + API::Node spanner() { // older versions result = API::moduleImport("@google-cloud/spanner") or @@ -338,7 +338,7 @@ private module Spanner { /** * Gets a node that refers to an instance of the `Database` class. */ - API::Feature database() { + API::Node database() { result = spanner().getReturn().getMember("instance").getReturn().getMember("database").getReturn() } @@ -346,14 +346,14 @@ private module Spanner { /** * Gets a node that refers to an instance of the `v1.SpannerClient` class. */ - API::Feature v1SpannerClient() { + API::Node v1SpannerClient() { result = spanner().getMember("v1").getMember("SpannerClient").getInstance() } /** * Gets a node that refers to a transaction object. */ - API::Feature transaction() { + API::Node transaction() { result = database().getMember("runTransaction").getParameter(0).getParameter(1) } diff --git a/javascript/ql/src/semmle/javascript/frameworks/SystemCommandExecutors.qll b/javascript/ql/src/semmle/javascript/frameworks/SystemCommandExecutors.qll index 9f29ac2ceef0..c6c59ccaa4bd 100644 --- a/javascript/ql/src/semmle/javascript/frameworks/SystemCommandExecutors.qll +++ b/javascript/ql/src/semmle/javascript/frameworks/SystemCommandExecutors.qll @@ -115,14 +115,14 @@ private class RemoteCommandExecutor extends SystemCommandExecution, DataFlow::In this = API::moduleImport("remote-exec").getReturn().getAUse() and cmdArg = 1 or - exists(API::Feature ssh2, API::Feature client | + 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(API::Feature ssh2stream | + exists(API::Node ssh2stream | ssh2stream = API::moduleImport("ssh2-streams").getMember("SSH2Stream") and this = ssh2stream.getInstance().getMember("exec").getReturn().getAUse() and cmdArg = 1 diff --git a/javascript/ql/src/semmle/javascript/security/dataflow/MissingRateLimiting.qll b/javascript/ql/src/semmle/javascript/security/dataflow/MissingRateLimiting.qll index dd8224ab4764..9b26660c6f0e 100644 --- a/javascript/ql/src/semmle/javascript/security/dataflow/MissingRateLimiting.qll +++ b/javascript/ql/src/semmle/javascript/security/dataflow/MissingRateLimiting.qll @@ -168,7 +168,7 @@ class RouteHandlerLimitedByExpressLimiter extends RateLimitedRouteHandlerExpr { class RateLimiterFlexibleRateLimiter extends DataFlow::FunctionNode { RateLimiterFlexibleRateLimiter() { exists( - string rateLimiterClassName, API::Feature rateLimiterClass, API::Feature rateLimiterConsume, + string rateLimiterClassName, API::Node rateLimiterClass, API::Node rateLimiterConsume, DataFlow::ParameterNode request | rateLimiterClassName.matches("RateLimiter%") and diff --git a/javascript/ql/test/ApiGraphs/VerifyAssertions.qll b/javascript/ql/test/ApiGraphs/VerifyAssertions.qll index 489a94986a13..4c92792cce5e 100644 --- a/javascript/ql/test/ApiGraphs/VerifyAssertions.qll +++ b/javascript/ql/test/ApiGraphs/VerifyAssertions.qll @@ -20,7 +20,7 @@ import javascript -private DataFlow::Node getNode(API::Feature nd, string kind) { +private DataFlow::Node getNode(API::Node nd, string kind) { kind = "def" and result = nd.getARhs() or @@ -58,7 +58,7 @@ class Assertion extends Comment { int getPathLength() { result = max(int i | exists(getEdgeLabel(i))) + 1 } - API::Feature lookup(int i) { + API::Node lookup(int i) { i = getPathLength() and result = API::root() or @@ -70,7 +70,7 @@ class Assertion extends Comment { predicate holds() { getLoc(getNode(lookup(0), expectedKind)) = expectedLoc } string tryExplainFailure() { - exists(int i, API::Feature nd, string prefix, string suffix | + exists(int i, API::Node nd, string prefix, string suffix | nd = lookup(i) and i > 0 and not exists(lookup([0 .. i - 1])) and @@ -85,7 +85,7 @@ class Assertion extends Comment { result = prefix + " " + suffix ) or - exists(API::Feature nd, string kind | nd = lookup(0) | + 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." From 423d87b8127f70dcd852c47f5247e1ee771b121f Mon Sep 17 00:00:00 2001 From: Max Schaefer Date: Mon, 7 Sep 2020 14:02:37 +0100 Subject: [PATCH 24/26] JavaScript: Rename `TNode` to `TApiNode`. This prevents spurious recomputation of a cached stage. --- .../ql/src/semmle/javascript/ApiGraphs.qll | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/javascript/ql/src/semmle/javascript/ApiGraphs.qll b/javascript/ql/src/semmle/javascript/ApiGraphs.qll index 344d883938ac..96c6b231fc02 100644 --- a/javascript/ql/src/semmle/javascript/ApiGraphs.qll +++ b/javascript/ql/src/semmle/javascript/ApiGraphs.qll @@ -19,7 +19,7 @@ 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::TNode { + class Node extends Impl::TApiNode { /** * Gets a data-flow node corresponding to a use of the API component represented by this node. * @@ -294,7 +294,7 @@ module API { cached private module Impl { cached - newtype TNode = + newtype TApiNode = MkRoot() or MkModuleDef(string m) { exists(MkModuleExport(m)) } or MkModuleUse(string m) { exists(MkModuleImport(m)) } or @@ -369,7 +369,7 @@ module API { * incoming edge from `base` labeled `lbl` in the API graph. */ cached - predicate rhs(TNode base, string lbl, DataFlow::Node rhs) { + predicate rhs(TApiNode base, string lbl, DataFlow::Node rhs) { hasSemantics(rhs) and ( base = MkRoot() and @@ -435,7 +435,7 @@ module API { * Holds if `rhs` is the right-hand side of a definition of node `nd`. */ cached - predicate rhs(TNode nd, DataFlow::Node rhs) { + predicate rhs(TApiNode nd, DataFlow::Node rhs) { exists(string m | nd = MkModuleExport(m) | exports(m, rhs)) or nd = MkDef(rhs) @@ -451,7 +451,7 @@ module API { * `lbl` in the API graph. */ cached - predicate use(TNode base, string lbl, DataFlow::Node ref) { + predicate use(TApiNode base, string lbl, DataFlow::Node ref) { hasSemantics(ref) and ( base = MkRoot() and @@ -507,7 +507,7 @@ module API { * Holds if `ref` is a use of node `nd`. */ cached - predicate use(TNode nd, DataFlow::Node ref) { + 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 @@ -606,7 +606,7 @@ module API { * Holds if there is an edge from `pred` to `succ` in the API graph that is labeled with `lbl`. */ cached - predicate edge(TNode pred, string lbl, TNode succ) { + predicate edge(TApiNode pred, string lbl, TApiNode succ) { exists(string m | pred = MkRoot() and lbl = Label::mod(m) @@ -669,11 +669,11 @@ module API { /** * Holds if there is an edge from `pred` to `succ` in the API graph. */ - private predicate edge(TNode pred, TNode succ) { edge(pred, _, succ) } + 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(TNode nd) = shortestDistances(MkRoot/0, edge/2)(_, nd, result) + int distanceFromRoot(TApiNode nd) = shortestDistances(MkRoot/0, edge/2)(_, nd, result) } import Label as EdgeLabel From b8a492473b557742f7c2b7f31fad2e4c03766026 Mon Sep 17 00:00:00 2001 From: Max Schaefer Date: Mon, 7 Sep 2020 14:26:10 +0100 Subject: [PATCH 25/26] JavaScript: Stop tracking canonical function names in API graphs. This blows up on the TypeScript compiler, and is likely to be much less useful than tracking type names and namespaces, which we still do. --- javascript/ql/src/semmle/javascript/ApiGraphs.qll | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/javascript/ql/src/semmle/javascript/ApiGraphs.qll b/javascript/ql/src/semmle/javascript/ApiGraphs.qll index 96c6b231fc02..0b75c31d74b0 100644 --- a/javascript/ql/src/semmle/javascript/ApiGraphs.qll +++ b/javascript/ql/src/semmle/javascript/ApiGraphs.qll @@ -350,15 +350,13 @@ module API { private predicate isUsed(CanonicalName n) { exists(n.(TypeName).getAnAccess()) or - exists(n.(Namespace).getAnAccess()) or - exists(InvokeExpr invk | invk.getResolvedCalleeName() = n) + exists(n.(Namespace).getAnAccess()) } private predicate isDefined(CanonicalName n) { exists(ASTNode def | def = n.(TypeName).getADefinition() or - def = n.(Namespace).getADefinition() or - def = n.(CanonicalFunctionName).getADefinition() + def = n.(Namespace).getADefinition() | not def.isAmbient() ) From b71a8e2ad0d47b270bdd7853d0437087b4077153 Mon Sep 17 00:00:00 2001 From: Max Schaefer Date: Thu, 10 Sep 2020 08:44:06 +0100 Subject: [PATCH 26/26] JavaScript: Expose an API-graph predicate that is useful for flow summaries. --- javascript/ql/src/semmle/javascript/ApiGraphs.qll | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/javascript/ql/src/semmle/javascript/ApiGraphs.qll b/javascript/ql/src/semmle/javascript/ApiGraphs.qll index 0b75c31d74b0..4c92212df576 100644 --- a/javascript/ql/src/semmle/javascript/ApiGraphs.qll +++ b/javascript/ql/src/semmle/javascript/ApiGraphs.qll @@ -169,9 +169,9 @@ module API { predicate refersTo(Node that) { this.getARhs() = that.getAUse() } /** - * Gets the unique data-flow that gives rise to this node, if any. + * Gets the data-flow node that gives rise to this node, if any. */ - private DataFlow::Node getRepresentativeNode() { + DataFlow::Node getInducingNode() { this = Impl::MkClassInstance(result) or this = Impl::MkUse(result) or this = Impl::MkDef(result) or @@ -186,9 +186,9 @@ module API { * parameters are zero. */ predicate hasLocationInfo(string path, int startline, int startcol, int endline, int endcol) { - getRepresentativeNode().hasLocationInfo(path, startline, startcol, endline, endcol) + getInducingNode().hasLocationInfo(path, startline, startcol, endline, endcol) or - not exists(getRepresentativeNode()) and + not exists(getInducingNode()) and path = "" and startline = 0 and startcol = 0 and