diff --git a/javascript/ql/lib/semmle/javascript/ApiGraphs.qll b/javascript/ql/lib/semmle/javascript/ApiGraphs.qll index 1a96e25b3b9f..e8717871ac27 100644 --- a/javascript/ql/lib/semmle/javascript/ApiGraphs.qll +++ b/javascript/ql/lib/semmle/javascript/ApiGraphs.qll @@ -122,6 +122,7 @@ module API { * Although one may think of API graphs as a tool to find certain program elements in the codebase, * it can lead to some situations where intuition does not match what works best in practice. */ + overlay[local?] class Node extends Impl::TApiNode { /** * Get a data-flow node where this value may flow after entering the current codebase. @@ -545,7 +546,7 @@ module API { this = Impl::MkClassInstance(result) or this = Impl::MkUse(result) or this = Impl::MkDef(result) or - this = Impl::MkSyntheticCallbackArg(_, _, result) + this = Impl::MkSyntheticCallbackArg(result) } /** @@ -579,7 +580,31 @@ module API { * Gets a textual representation of this node. */ string toString() { - none() // defined in subclasses + this = Impl::MkRoot() and result = "root" + or + exists(string m | this = Impl::MkModuleDef(m) | result = "module def " + m) + or + exists(string m | this = Impl::MkModuleUse(m) | result = "module use " + m) + or + exists(string m | this = Impl::MkModuleExport(m) | result = "module export " + m) + or + exists(string m | this = Impl::MkModuleImport(m) | result = "module import " + m) + or + exists(string m, string e | this = Impl::MkTypeUse(m, e) | + result = "type use " + m + "::" + e + ) + or + exists(DataFlow::SourceNode cls | this = Impl::MkClassInstance(cls) | + result = "instance of " + cls.toString() + ) + or + exists(DataFlow::Node nd | this = Impl::MkDef(nd) | result = "def " + nd.toString()) + or + exists(DataFlow::Node nd | this = Impl::MkUse(nd) | result = "use " + nd.toString()) + or + exists(DataFlow::InvokeNode nd | this = Impl::MkSyntheticCallbackArg(nd) | + result = "callback arg " + nd.toString() + ) } /** @@ -607,19 +632,13 @@ module API { } /** The root node of an API graph. */ - class Root extends Node, Impl::MkRoot { - override string toString() { result = "root" } - } + class Root extends Node, Impl::MkRoot { } /** A node corresponding to a definition of an API component. */ - class Definition extends Node, Impl::TDef { - override string toString() { result = "def " + this.getPath() } - } + class Definition extends Node, Impl::TDef { } /** A node corresponding to the use of an API component. */ - class Use extends Node, Impl::TUse { - override string toString() { result = "use " + this.getPath() } - } + class Use extends Node, Impl::TUse { } /** Gets the root node. */ Root root() { any() } @@ -676,17 +695,21 @@ module API { * Imports and exports are considered entry points by default, but additional entry points may * be added by extending this class. Typical examples include global variables. */ + overlay[local] abstract class EntryPoint extends string { bindingset[this] EntryPoint() { any() } /** Gets a data-flow node where a value enters the current codebase through this entry-point. */ + overlay[global] DataFlow::SourceNode getASource() { none() } /** Gets a data-flow node where a value leaves the current codebase through this entry-point. */ + overlay[global] DataFlow::Node getASink() { none() } /** Gets an API-node for this entry point. */ + overlay[global] API::Node getANode() { result = root().getASuccessor(Label::entryPoint(this)) } } @@ -731,49 +754,54 @@ module API { */ cached private module Impl { + private predicate hasTypeUse(string moduleName, string exportName) { + any(TypeAnnotation n).hasUnderlyingType(moduleName, exportName) + } + + overlay[local] + private predicate hasTypeUseLocal(string moduleName, string exportName) = + forceLocal(hasTypeUse/2)(moduleName, exportName) + + overlay[local] cached newtype TApiNode = MkRoot() or MkModuleDef(string m) { exists(MkModuleExport(m)) } or MkModuleUse(string m) { exists(MkModuleImport(m)) } or - MkModuleExport(string m) { - exists(Module mod | mod = importableModule(m) | - // exclude modules that don't actually export anything - exports(m, _) - or - exports(m, _, _) - or - exists(NodeModule nm | nm = mod | - exists(Ssa::implicitInit([nm.getModuleVariable(), nm.getExportsVariable()])) - ) - ) - } or - MkModuleImport(string m) { - imports(_, m) + MkModuleExport(string m) { isDeclaredPackageName(m) } or + MkModuleImport(string m) { isImportedPackageName(m) } or + MkClassInstance(DataFlow::SourceNode cls) { + cls = any(Function f).flow() or - any(TypeAnnotation n).hasUnderlyingType(m, _) - } or - MkClassInstance(DataFlow::ClassNode cls) { needsDefNode(cls) } or - MkDef(DataFlow::Node nd) { rhs(_, _, nd) } or - MkUse(DataFlow::Node nd) { use(_, _, nd) } or - /** A use of a TypeScript type. */ - MkTypeUse(string moduleName, string exportName) { - any(TypeAnnotation n).hasUnderlyingType(moduleName, exportName) + cls = any(ClassDefinition c).flow() } or - MkSyntheticCallbackArg(DataFlow::Node src, int bound, DataFlow::InvokeNode nd) { - trackUseNode(src, true, bound, "").flowsTo(nd.getCalleeNode()) - } - - private predicate needsDefNode(DataFlow::ClassNode cls) { - hasSemantics(cls) and - ( - cls = trackDefNode(_) + MkDef(DataFlow::Node nd) { + nd = any(DataFlow::PropWrite w).getRhs() or - cls.getAnInstanceReference() = trackDefNode(_) + nd = any(DataFlow::FunctionNode fn).getReturnNode() or - needsDefNode(cls.getADirectSubClass()) - ) - } + nd = any(DataFlow::FunctionNode fn).getAReturn() + or + nd = any(DataFlow::FunctionNode fn).getExceptionalReturn() + or + nd = any(DataFlow::CallNode c).getReceiver() + or + nd = any(DataFlow::InvokeNode i).getAnArgument() + or + nd = any(DataFlow::InvokeNode i).getASpreadArgument() + or + nd = any(ThrowStmt stmt).getExpr().flow() + or + nd = any(ExportDeclaration decl).getDirectSourceNode(_) + or + nd = any(MemberDeclaration m).getInit().flow() + or + nd = any(ClassDefinition cls | exists(cls.getADecorator())).flow() + } or + MkUse(DataFlow::Node nd) { nd instanceof DataFlow::SourceNode } or + /** A use of a TypeScript type. */ + MkTypeUse(string moduleName, string exportName) { hasTypeUseLocal(moduleName, exportName) } or + MkSyntheticCallbackArg(DataFlow::InvokeNode nd) class TDef = MkModuleDef or TNonModuleDef; @@ -783,698 +811,976 @@ module API { private predicate hasSemantics(DataFlow::Node nd) { not nd.getTopLevel().isExterns() } - /** Holds if `imp` is an import of module `m`. */ - private predicate imports(DataFlow::Node imp, string m) { - imp = DataFlow::moduleImport(m) and - // path must not start with a dot or a slash - m.regexpMatch("[^./].*") and - hasSemantics(imp) - } - - /** - * Holds if `rhs` is the right-hand side of a definition of a node that should have an - * incoming edge from `base` labeled `lbl` in the API graph. - */ - cached - predicate rhs(TApiNode base, Label::ApiLabel lbl, DataFlow::Node rhs) { - hasSemantics(rhs) and - ( - base = MkRoot() and - exists(EntryPoint e | - lbl = Label::entryPoint(e) and - rhs = e.getASink() - ) - or - exists(string m, string prop | - base = MkModuleExport(m) and - lbl = Label::member(prop) and - exports(m, prop, rhs) - ) - or - exists(DataFlow::Node def, DataFlow::SourceNode pred | - rhs(base, def) and pred = trackDefNode(def) - | - // from `x` to a definition of `x.prop` - exists(DataFlow::PropWrite pw | pw = pred.getAPropertyWrite() | - lbl = Label::memberFromRef(pw) and - rhs = pw.getRhs() + bindingset[nd] + pragma[inline_late] + private predicate hasSemanticsLate(DataFlow::Node nd) { hasSemantics(nd) } + + private signature module StageInputSig { + /** Holds if `node` should be seen as a use-node root, in addition to module imports (which are the usual roots). */ + predicate isAdditionalUseRoot(Node node); + + /** Holds if `node` should be seen as a def-node root, in addition to module exports (which are the usual roots). */ + predicate isAdditionalDefRoot(Node node); + + /** + * Holds if `node` is in a file that is considered "active" in this stage, meaning that we allow outgoing labelled edges + * to be materialised from here, and continue API graph construction from the successors' edges. + * + * Note that the "additional roots" contributed by the stage inputs may be in an inactive file but can be tracked to a node in an + * active file. + * This predicate should thus not be used to block the tracking of use/def nodes, but only block the creation of new labelled edges. + */ + bindingset[node] + predicate inActiveFile(DataFlow::Node node); + } + + private module Stage { + /** + * 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. + */ + predicate rhs(TApiNode base, Label::ApiLabel lbl, DataFlow::Node rhs) { + hasSemantics(rhs) and + ( + base = MkRoot() and + exists(EntryPoint e | + lbl = Label::entryPoint(e) and + rhs = e.getASink() ) or - // special case: from `require('m')` to an export of `prop` in `m` - exists(Import imp, Module m, string prop | - pred = imp.getImportedModuleNodeStrict() and - m = imp.getImportedModule() and + exists(string m, string prop | + base = MkModuleExport(m) and lbl = Label::member(prop) and - rhs = m.getAnExportedValue(prop) + exports(m, prop, rhs) ) or - // In general, turn store steps into member steps for def-nodes - exists(string prop | - PreCallGraphStep::storeStep(rhs, pred, prop) and - lbl = Label::member(prop) and - not DataFlow::PseudoProperties::isPseudoProperty(prop) + exists(DataFlow::Node def, DataFlow::SourceNode pred | + rhs(base, def) and pred = trackDefNode(def) + | + // from `x` to a definition of `x.prop` + exists(DataFlow::PropWrite pw | pw = pred.getAPropertyWrite() | + lbl = Label::memberFromRef(pw) and + rhs = pw.getRhs() + ) + or + // special case: from `require('m')` to an export of `prop` in `m` + exists(Import imp, Module m, string prop | + pred = imp.getImportedModuleNodeStrict() and + m = imp.getImportedModule() and + lbl = Label::member(prop) and + rhs = m.getAnExportedValue(prop) + ) + or + // In general, turn store steps into member steps for def-nodes + exists(string prop | + PreCallGraphStep::storeStep(rhs, pred, prop) and + lbl = Label::member(prop) and + not DataFlow::PseudoProperties::isPseudoProperty(prop) + ) + or + exists(DataFlow::ContentSet contents | + SummaryTypeTracker::basicStoreStep(rhs, pred.getALocalUse(), contents) and + lbl = Label::content(contents.getAStoreContent()) + ) + or + exists(DataFlow::FunctionNode fn | + fn = pred and + lbl = Label::return() + | + if fn.getFunction().isAsync() then rhs = fn.getReturnNode() else rhs = fn.getAReturn() + ) + or + lbl = Label::promised() and + SharedTypeTrackingStep::storeStep(rhs, pred, Promises::valueProp()) + or + lbl = Label::promisedError() and + SharedTypeTrackingStep::storeStep(rhs, pred, Promises::errorProp()) + or + // The return-value of a getter G counts as a definition of property G + // (Ordinary methods and properties are handled as PropWrite nodes) + exists(string name | lbl = Label::member(name) | + rhs = pred.(DataFlow::ObjectLiteralNode).getPropertyGetter(name).getAReturn() + or + rhs = + pred.(DataFlow::ClassNode) + .getStaticMember(name, DataFlow::MemberKind::getter()) + .getAReturn() + ) + or + // Handle rest parameters escaping into external code. For example: + // + // function foo(...rest) { + // externalFunc(rest); + // } + // + // Here, 'rest' reaches a def-node at the call to externalFunc, so we need to ensure + // the arguments passed to 'foo' are stored in the 'rest' array. + exists(Function fun, DataFlow::InvokeNode invoke, int argIndex, Parameter rest | + fun.getRestParameter() = rest and + rest.flow() = pred and + invoke.getACallee() = fun and + invoke.getArgument(argIndex) = rhs and + argIndex >= rest.getIndex() and + lbl = Label::member((argIndex - rest.getIndex()).toString()) + ) ) or - exists(DataFlow::ContentSet contents | - SummaryTypeTracker::basicStoreStep(rhs, pred.getALocalUse(), contents) and - lbl = Label::content(contents.getAStoreContent()) + exists(DataFlow::ClassNode cls, string name | + base = MkClassInstance(cls) and + lbl = Label::member(name) + | + rhs = cls.getInstanceMethod(name) + or + rhs = cls.getInstanceMember(name, DataFlow::MemberKind::getter()).getAReturn() ) or - exists(DataFlow::FunctionNode fn | - fn = pred and - lbl = Label::return() + exists(DataFlow::FunctionNode f | + f.getFunction().isAsync() and + base = MkDef(f.getReturnNode()) | - if fn.getFunction().isAsync() then rhs = fn.getReturnNode() else rhs = fn.getAReturn() + lbl = Label::promised() and + rhs = f.getAReturn() + or + lbl = Label::promisedError() and + rhs = f.getExceptionalReturn() ) or - lbl = Label::promised() and - SharedTypeTrackingStep::storeStep(rhs, pred, Promises::valueProp()) - or - lbl = Label::promisedError() and - SharedTypeTrackingStep::storeStep(rhs, pred, Promises::errorProp()) - or - // The return-value of a getter G counts as a definition of property G - // (Ordinary methods and properties are handled as PropWrite nodes) - exists(string name | lbl = Label::member(name) | - rhs = pred.(DataFlow::ObjectLiteralNode).getPropertyGetter(name).getAReturn() + exists(int i | argumentPassing(base, i, rhs) | + lbl = Label::parameter(i) or - rhs = - pred.(DataFlow::ClassNode) - .getStaticMember(name, DataFlow::MemberKind::getter()) - .getAReturn() + i = -1 and lbl = Label::receiver() ) or - // Handle rest parameters escaping into external code. For example: - // - // function foo(...rest) { - // externalFunc(rest); - // } - // - // Here, 'rest' reaches a def-node at the call to externalFunc, so we need to ensure - // the arguments passed to 'foo' are stored in the 'rest' array. - exists(Function fun, DataFlow::InvokeNode invoke, int argIndex, Parameter rest | - fun.getRestParameter() = rest and - rest.flow() = pred and - invoke.getACallee() = fun and - invoke.getArgument(argIndex) = rhs and - argIndex >= rest.getIndex() and - lbl = Label::member((argIndex - rest.getIndex()).toString()) + exists(int i | + spreadArgumentPassing(base, i, rhs) and + lbl = Label::spreadArgument(i) ) - ) - or - exists(DataFlow::ClassNode cls, string name | - base = MkClassInstance(cls) and - lbl = Label::member(name) - | - rhs = cls.getInstanceMethod(name) or - rhs = cls.getInstanceMember(name, DataFlow::MemberKind::getter()).getAReturn() - ) - or - exists(DataFlow::FunctionNode f | - f.getFunction().isAsync() and - base = MkDef(f.getReturnNode()) - | - lbl = Label::promised() and - rhs = f.getAReturn() - or - lbl = Label::promisedError() and - rhs = f.getExceptionalReturn() + exists(DataFlow::SourceNode src, DataFlow::PropWrite pw | + use(base, src) and pw = trackUseNode(src).getAPropertyWrite() and rhs = pw.getRhs() + | + lbl = Label::memberFromRef(pw) + ) ) or - exists(int i | argumentPassing(base, i, rhs) | - lbl = Label::parameter(i) - or - i = -1 and lbl = Label::receiver() - ) + decoratorDualEdge(base, lbl, rhs) or - exists(int i | - spreadArgumentPassing(base, i, rhs) and - lbl = Label::spreadArgument(i) - ) + decoratorRhsEdge(base, lbl, rhs) or - exists(DataFlow::SourceNode src, DataFlow::PropWrite pw | - use(base, src) and pw = trackUseNode(src).getAPropertyWrite() and rhs = pw.getRhs() - | - lbl = Label::memberFromRef(pw) + exists(DataFlow::PropWrite write | + decoratorPropEdge(base, lbl, write) and + rhs = write.getRhs() ) - ) - or - decoratorDualEdge(base, lbl, rhs) - or - decoratorRhsEdge(base, lbl, rhs) - or - exists(DataFlow::PropWrite write | - decoratorPropEdge(base, lbl, write) and - rhs = write.getRhs() - ) - } + } - /** - * Holds if `arg` is passed as the `i`th argument to a use of `base`, either by means of a - * full invocation, or in a partial function application. - * - * The receiver is considered to be argument -1. - */ - private predicate argumentPassing(TApiNode base, int i, DataFlow::Node arg) { - exists(DataFlow::Node use, DataFlow::SourceNode pred, int bound | - use(base, use) and pred = trackUseNode(use, _, bound, "") - | - arg = pred.getAnInvocation().getArgument(i - bound) - or - arg = pred.getACall().getReceiver() and - bound = 0 and - i = -1 - or - exists(DataFlow::PartialInvokeNode pin, DataFlow::Node callback | pred.flowsTo(callback) | - pin.isPartialArgument(callback, arg, i - bound) + /** + * Holds if `arg` is passed as the `i`th argument to a use of `base`, either by means of a + * full invocation, or in a partial function application. + * + * The receiver is considered to be argument -1. + */ + private predicate argumentPassing(TApiNode base, int i, DataFlow::Node arg) { + exists(DataFlow::Node use, DataFlow::SourceNode pred, int bound | + use(base, use) and pred = trackUseNode(use, _, bound, "") + | + arg = pred.getAnInvocation().getArgument(i - bound) or - arg = pin.getBoundReceiver(callback) and + arg = pred.getACall().getReceiver() and bound = 0 and i = -1 + or + exists(DataFlow::PartialInvokeNode pin, DataFlow::Node callback | pred.flowsTo(callback) | + pin.isPartialArgument(callback, arg, i - bound) + or + arg = pin.getBoundReceiver(callback) and + bound = 0 and + i = -1 + ) ) - ) - } + } - pragma[nomagic] - private int firstSpreadIndex(InvokeExpr expr) { - result = min(int i | expr.getArgument(i) instanceof SpreadElement) - } + pragma[nomagic] + private int firstSpreadIndex(InvokeExpr expr) { + result = min(int i | expr.getArgument(i) instanceof SpreadElement) + } - pragma[nomagic] - private InvokeExpr getAnInvocationWithSpread(DataFlow::SourceNode node, int i) { - result = node.getAnInvocation().asExpr() and - i = firstSpreadIndex(result) - } + pragma[nomagic] + private InvokeExpr getAnInvocationWithSpread(DataFlow::SourceNode node, int i) { + result = node.getAnInvocation().asExpr() and + i = firstSpreadIndex(result) + } - private predicate spreadArgumentPassing(TApiNode base, int i, DataFlow::Node spreadArray) { - exists( - DataFlow::Node use, DataFlow::SourceNode pred, int bound, InvokeExpr invoke, int spreadPos - | - use(base, use) and - pred = trackUseNode(use, _, bound, "") and - invoke = getAnInvocationWithSpread(pred, spreadPos) and - spreadArray = invoke.getArgument(spreadPos).(SpreadElement).getOperand().flow() and - i = bound + spreadPos - ) - } + private predicate spreadArgumentPassing(TApiNode base, int i, DataFlow::Node spreadArray) { + exists( + DataFlow::Node use, DataFlow::SourceNode pred, int bound, InvokeExpr invoke, int spreadPos + | + use(base, use) and + pred = trackUseNode(use, _, bound, "") and + invoke = getAnInvocationWithSpread(pred, spreadPos) and + spreadArray = invoke.getArgument(spreadPos).(SpreadElement).getOperand().flow() and + i = bound + spreadPos + ) + } - /** - * Holds if `rhs` is the right-hand side of a definition of node `nd`. - */ - cached - predicate rhs(TApiNode nd, DataFlow::Node rhs) { - exists(string m | nd = MkModuleExport(m) | exports(m, rhs)) - or - nd = MkDef(rhs) - } + /** + * Holds if `rhs` is the right-hand side of a definition of node `nd`. + */ + predicate rhs(TApiNode nd, DataFlow::Node rhs) { + (S::inActiveFile(rhs) or S::isAdditionalDefRoot(nd)) and + exists(string m | nd = MkModuleExport(m) | exports(m, rhs)) + or + rhs(_, _, rhs) and + S::inActiveFile(rhs) and + nd = MkDef(rhs) + or + S::isAdditionalDefRoot(nd) and + nd = mkDefLate(rhs) + } - /** - * Holds if `ref` is a read of a property described by `lbl` on `pred`, and - * `propDesc` is compatible with that property, meaning it is either the - * name of the property itself or the empty string. - */ - pragma[noinline] - private predicate propertyRead( - DataFlow::SourceNode pred, string propDesc, Label::ApiLabel lbl, DataFlow::Node ref - ) { - ref = pred.getAPropertyRead() and - lbl = Label::memberFromRef(ref) and - ( - lbl = Label::member(propDesc) + /** + * Holds if `ref` is a read of a property described by `lbl` on `pred`, and + * `propDesc` is compatible with that property, meaning it is either the + * name of the property itself or the empty string. + */ + pragma[noinline] + private predicate propertyRead( + DataFlow::SourceNode pred, string propDesc, Label::ApiLabel lbl, DataFlow::Node ref + ) { + ref = pred.getAPropertyRead() and + lbl = Label::memberFromRef(ref) and + ( + lbl = Label::member(propDesc) + or + propDesc = "" + ) or - propDesc = "" - ) - or - SharedTypeTrackingStep::loadStep(pred.getALocalUse(), ref, Promises::valueProp()) and - lbl = Label::promised() and - (propDesc = Promises::valueProp() or propDesc = "") - or - SharedTypeTrackingStep::loadStep(pred.getALocalUse(), ref, Promises::errorProp()) and - lbl = Label::promisedError() and - (propDesc = Promises::errorProp() or propDesc = "") - } + SharedTypeTrackingStep::loadStep(pred.getALocalUse(), ref, Promises::valueProp()) and + lbl = Label::promised() and + (propDesc = Promises::valueProp() or propDesc = "") + or + SharedTypeTrackingStep::loadStep(pred.getALocalUse(), ref, Promises::errorProp()) and + lbl = Label::promisedError() and + (propDesc = Promises::errorProp() or propDesc = "") + } - pragma[nomagic] - private DataFlow::ClassNode getALocalSubclass(DataFlow::SourceNode node) { - result.getASuperClassNode().getALocalSource() = node - } + pragma[nomagic] + private DataFlow::ClassNode getALocalSubclass(DataFlow::SourceNode node) { + result.getASuperClassNode().getALocalSource() = node + } - bindingset[node] - pragma[inline_late] - private DataFlow::ClassNode getALocalSubclassFwd(DataFlow::SourceNode node) { - result = getALocalSubclass(node) - } + bindingset[node] + pragma[inline_late] + private DataFlow::ClassNode getALocalSubclassFwd(DataFlow::SourceNode node) { + result = getALocalSubclass(node) + } - /** - * Holds if `ref` is a use of a node that should have an incoming edge from `base` labeled - * `lbl` in the API graph. - */ - cached - predicate use(TApiNode base, Label::ApiLabel lbl, DataFlow::Node ref) { - hasSemantics(ref) and - ( - base = MkRoot() and - exists(EntryPoint e | - lbl = Label::entryPoint(e) and - ref = e.getASource() + /** + * Holds if `ref` is a use of a node that should have an incoming edge from `base` labeled + * `lbl` in the API graph. + */ + predicate use(TApiNode base, Label::ApiLabel lbl, DataFlow::Node ref) { + hasSemantics(ref) and + ( + base = MkRoot() and + exists(EntryPoint e | + lbl = Label::entryPoint(e) and + ref = e.getASource() and + S::inActiveFile(ref) + ) + or + // property reads + exists(DataFlow::SourceNode src, DataFlow::SourceNode pred, string propDesc | + use(base, src) and + pred = trackUseNode(src, false, 0, propDesc) and + propertyRead(pred, propDesc, lbl, ref) and + // `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) + ) + or + exists(DataFlow::SourceNode src, DataFlow::SourceNode pred | + use(base, src) and pred = trackUseNode(src) + | + lbl = Label::instance() and + ref = pred.getAnInstantiation() + or + lbl = Label::return() and + ref = pred.getAnInvocation() + or + lbl = Label::forwardingFunction() and + DataFlow::functionForwardingStep(pred.getALocalUse(), ref) + or + exists(DataFlow::ClassNode cls | + lbl = Label::instance() and + cls = getALocalSubclassFwd(pred).getADirectSubClass*() + | + ref = cls.getAReceiverNode() + or + ref = cls.getAClassReference().getAnInstantiation() + ) + or + exists(string prop | + PreCallGraphStep::loadStep(pred.getALocalUse(), ref, prop) and + lbl = Label::member(prop) and + // avoid generating member edges like "$arrayElement$" + not DataFlow::PseudoProperties::isPseudoProperty(prop) + ) + or + exists(DataFlow::ContentSet contents | + SummaryTypeTracker::basicLoadStep(pred.getALocalUse(), ref, contents) and + lbl = Label::content(contents.getAStoreContent()) + ) + ) + or + exists(DataFlow::Node def, DataFlow::FunctionNode fn | + rhs(base, def) and fn = trackDefNode(def) + | + exists(int i | + lbl = Label::parameter(i) and + ref = fn.getParameter(i) + ) + or + lbl = Label::receiver() and + ref = fn.getReceiver() + ) + or + exists(DataFlow::Node def, DataFlow::ClassNode cls, int i | + rhs(base, def) and cls = trackDefNode(def) + | + lbl = Label::parameter(i) and + ref = cls.getConstructor().getParameter(i) + ) + or + exists(string moduleName, string exportName | + base = MkTypeUse(moduleName, exportName) and + lbl = Label::instance() and + ref.(DataFlow::SourceNode).hasUnderlyingType(moduleName, exportName) + ) + or + exists(DataFlow::InvokeNode call | + base = MkSyntheticCallbackArg(call) and + lbl = Label::parameter(1) and + ref = awaited(call) + ) + or + // Handle promisified object member access: promisify(obj).member should be treated as obj.member (promisified) + exists( + Promisify::PromisifyAllCall promisifiedObj, DataFlow::SourceNode originalObj, + string member + | + originalObj.flowsTo(promisifiedObj.getArgument(0)) and + use(base, originalObj) and + lbl = Label::member(member) and + ref = promisifiedObj.getAPropertyRead(member) + ) + or + decoratorDualEdge(base, lbl, ref) + or + decoratorUseEdge(base, lbl, ref) + or + // for fields and accessors, mark the reads as use-nodes + decoratorPropEdge(base, lbl, ref.(DataFlow::PropRead)) + ) + } + + /** Holds if `base` is a use-node that flows to the decorator expression of the given decorator. */ + pragma[nomagic] + private predicate useNodeFlowsToDecorator(TApiNode base, Decorator decorator) { + exists(DataFlow::SourceNode decoratorSrc | + use(base, decoratorSrc) and + trackUseNode(decoratorSrc).flowsToExpr(decorator.getExpression()) + ) + } + + /** + * Holds if `ref` corresponds to both a use and def-node that should have an incoming edge from `base` labelled `lbl`. + * + * This happens because the decorated value escapes into the decorator function, and is then replaced + * by the function's return value. In the JS analysis we generally assume decorators return their input, + * but library models may want to find the return value. + */ + private predicate decoratorDualEdge(TApiNode base, Label::ApiLabel lbl, DataFlow::Node ref) { + exists(ClassDefinition cls | + useNodeFlowsToDecorator(base, cls.getADecorator()) and + lbl = Label::decoratedClass() and + ref = DataFlow::valueNode(cls) ) or - // property reads - exists(DataFlow::SourceNode src, DataFlow::SourceNode pred, string propDesc | - use(base, src) and - pred = trackUseNode(src, false, 0, propDesc) and - propertyRead(pred, propDesc, lbl, ref) and - // `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) + exists(MethodDefinition method | + useNodeFlowsToDecorator(base, method.getADecorator()) and + not method instanceof AccessorMethodDefinition and + lbl = Label::decoratedMember() and + ref = DataFlow::valueNode(method.getBody()) + ) + } + + /** Holds if `ref` is a use that should have an incoming edge from `base` labelled `lbl`, induced by a decorator. */ + private predicate decoratorUseEdge(TApiNode base, Label::ApiLabel lbl, DataFlow::Node ref) { + exists(SetterMethodDefinition accessor | + useNodeFlowsToDecorator(base, + [accessor.getADecorator(), accessor.getCorrespondingGetter().getADecorator()]) and + lbl = Label::decoratedMember() and + ref = DataFlow::parameterNode(accessor.getBody().getParameter(0)) ) or - exists(DataFlow::SourceNode src, DataFlow::SourceNode pred | - use(base, src) and pred = trackUseNode(src) + exists(Parameter param | + useNodeFlowsToDecorator(base, param.getADecorator()) and + lbl = Label::decoratedParameter() and + ref = DataFlow::parameterNode(param) + ) + } + + /** Holds if `rhs` is a def node that should have an incoming edge from `base` labelled `lbl`, induced by a decorator. */ + private predicate decoratorRhsEdge(TApiNode base, Label::ApiLabel lbl, DataFlow::Node rhs) { + exists(GetterMethodDefinition accessor | + useNodeFlowsToDecorator(base, + [accessor.getADecorator(), accessor.getCorrespondingSetter().getADecorator()]) and + lbl = Label::decoratedMember() and + rhs = DataFlow::valueNode(accessor.getBody().getAReturnedExpr()) + ) + } + + /** + * Holds if `ref` is a reference to a field/accessor that should have an incoming edge from base labelled `lbl`. + * + * Since fields do not have their own data-flow nodes, we generate a node for each read or write. + * For property writes, the right-hand side becomes a def-node and property reads become use-nodes. + * + * For accessors this predicate computes each use of the accessor. + * The return value inside the accessor is computed by the `decoratorRhsEdge` predicate. + */ + private predicate decoratorPropEdge(TApiNode base, Label::ApiLabel lbl, DataFlow::PropRef ref) { + exists(MemberDefinition fieldLike, DataFlow::ClassNode cls | + fieldLike instanceof FieldDefinition + or + fieldLike instanceof AccessorMethodDefinition | - lbl = Label::instance() and - ref = pred.getAnInstantiation() + useNodeFlowsToDecorator(base, fieldLike.getADecorator()) and + lbl = Label::decoratedMember() and + cls = fieldLike.getDeclaringClass().flow() and + ( + fieldLike.isStatic() and + ref = cls.getAClassReference().getAPropertyReference(fieldLike.getName()) + or + not fieldLike.isStatic() and + ref = cls.getAnInstanceReference().getAPropertyReference(fieldLike.getName()) + ) + ) + } + + private predicate needsDefNode(DataFlow::ClassNode cls) { + hasSemantics(cls) and + ( + cls = trackDefNode(_) or - lbl = Label::return() and - ref = pred.getAnInvocation() + cls.getAnInstanceReference() = trackDefNode(_) or - lbl = Label::forwardingFunction() and - DataFlow::functionForwardingStep(pred.getALocalUse(), ref) + needsDefNode(cls.getADirectSubClass()) or - exists(DataFlow::ClassNode cls | - lbl = Label::instance() and - cls = getALocalSubclassFwd(pred).getADirectSubClass*() - | - ref = cls.getAReceiverNode() - or - ref = cls.getAClassReference().getAnInstantiation() + S::isAdditionalDefRoot(MkClassInstance(cls)) + or + S::isAdditionalUseRoot(MkClassInstance(cls)) // These are also tracked as use-nodes + ) + } + + /** + * Holds if `ref` is a use of node `nd`. + */ + predicate use(TApiNode nd, DataFlow::Node ref) { + (S::inActiveFile(ref) or S::isAdditionalUseRoot(nd)) and + ( + exists(string m, Module mod | nd = MkModuleDef(m) and mod = importableModule(m) | + ref = DataFlow::moduleVarNode(mod) ) or - exists(string prop | - PreCallGraphStep::loadStep(pred.getALocalUse(), ref, prop) and - lbl = Label::member(prop) and - // avoid generating member edges like "$arrayElement$" - not DataFlow::PseudoProperties::isPseudoProperty(prop) + exists(string m, Module mod | nd = MkModuleExport(m) and mod = importableModule(m) | + ref = DataFlow::exportsVarNode(mod) + or + exists(DataFlow::Node base | use(MkModuleDef(m), base) | + ref = trackUseNode(base).getAPropertyRead("exports") + ) ) or - exists(DataFlow::ContentSet contents | - SummaryTypeTracker::basicLoadStep(pred.getALocalUse(), ref, contents) and - lbl = Label::content(contents.getAStoreContent()) + exists(string m | + nd = MkModuleImport(m) and + ref = DataFlow::moduleImport(m) ) ) or - exists(DataFlow::Node def, DataFlow::FunctionNode fn | - rhs(base, def) and fn = trackDefNode(def) - | - exists(int i | - lbl = Label::parameter(i) and - ref = fn.getParameter(i) - ) + exists(DataFlow::ClassNode cls | nd = MkClassInstance(cls) and needsDefNode(cls) | + ref = cls.getAReceiverNode() or - lbl = Label::receiver() and - ref = fn.getReceiver() + ref = cls.(DataFlow::ClassNode).getAPrototypeReference() ) or - exists(DataFlow::Node def, DataFlow::ClassNode cls, int i | - rhs(base, def) and cls = trackDefNode(def) - | - lbl = Label::parameter(i) and - ref = cls.getConstructor().getParameter(i) + use(_, _, ref) and + S::inActiveFile(ref) and + nd = MkUse(ref) + or + S::isAdditionalUseRoot(nd) and + nd = mkUseLate(ref) + } + + bindingset[node] + pragma[inline_late] + private TApiNode mkUseLate(DataFlow::Node node) { result = MkUse(node) } + + bindingset[node] + pragma[inline_late] + private TApiNode mkDefLate(DataFlow::Node node) { result = MkDef(node) } + + private import semmle.javascript.dataflow.TypeTracking + + /** + * Gets a data-flow node to which `nd`, which is a use of an API-graph node, flows. + * + * The flow from `nd` to that node may be inter-procedural, and is further described by three + * flags: + * + * - `promisified`: if `true`, the flow goes through a promisification; + * - `boundArgs`: for function values, tracks how many arguments have been bound throughout + * the flow. To ensure termination, we somewhat arbitrarily constrain the number of bound + * arguments to be at most ten. + * - `prop`: if non-empty, the flow is only guaranteed to preserve the value of this property, + * and not necessarily the entire object. + */ + private DataFlow::SourceNode trackUseNode( + DataFlow::SourceNode nd, boolean promisified, int boundArgs, string prop, + DataFlow::TypeTracker t + ) { + t.start() and + use(_, nd) and + result = nd and + promisified = false and + boundArgs = 0 and + prop = "" + or + exists(Promisify::PromisifyCall promisify | + trackUseNode(nd, false, boundArgs, prop, t.continue()).flowsTo(promisify.getArgument(0)) and + promisified = true and + prop = "" and + result = promisify ) or - exists(string moduleName, string exportName | - base = MkTypeUse(moduleName, exportName) and - lbl = Label::instance() and - ref.(DataFlow::SourceNode).hasUnderlyingType(moduleName, exportName) + exists(DataFlow::PartialInvokeNode pin, DataFlow::Node pred, int predBoundArgs | + trackUseNode(nd, promisified, predBoundArgs, prop, t.continue()).flowsTo(pred) and + prop = "" and + result = pin.getBoundFunction(pred, boundArgs - predBoundArgs) and + boundArgs in [0 .. 10] ) or - exists(DataFlow::InvokeNode call | - base = MkSyntheticCallbackArg(_, _, call) and - lbl = Label::parameter(1) and - ref = awaited(call) + exists(DataFlow::SourceNode mid | + mid = trackUseNode(nd, promisified, boundArgs, prop, t) and + AdditionalUseStep::step(pragma[only_bind_out](mid), result) ) or - // Handle promisified object member access: promisify(obj).member should be treated as obj.member (promisified) - exists( - Promisify::PromisifyAllCall promisifiedObj, DataFlow::SourceNode originalObj, - string member + exists(DataFlow::Node pred, string preprop | + trackUseNode(nd, promisified, boundArgs, preprop, t.continue()).flowsTo(pred) and + promisified = false and + boundArgs = 0 and + SharedTypeTrackingStep::loadStoreStep(pred, result, prop) | - originalObj.flowsTo(promisifiedObj.getArgument(0)) and - use(base, originalObj) and - lbl = Label::member(member) and - ref = promisifiedObj.getAPropertyRead(member) + prop = preprop + or + preprop = "" ) or - decoratorDualEdge(base, lbl, ref) + t = useStep(nd, promisified, boundArgs, prop, result) + } + + /** + * Holds if `nd`, which is a use of an API-graph node, flows in zero or more potentially + * inter-procedural steps to some intermediate node, and then from that intermediate node to + * `res` in one step. The entire flow is described by the resulting `TypeTracker`. + * + * This predicate exists solely to enforce a better join order in `trackUseNode` above. + */ + pragma[noopt] + private DataFlow::TypeTracker useStep( + DataFlow::Node nd, boolean promisified, int boundArgs, string prop, DataFlow::Node res + ) { + exists(DataFlow::TypeTracker t, StepSummary summary, DataFlow::SourceNode prev | + prev = trackUseNode(nd, promisified, boundArgs, prop, t) and + StepSummary::step(prev, res, summary) and + result = t.append(summary) and + // Block argument-passing into 'this' when it determines the call target + not summary = CallReceiverStep() + ) + } + + private DataFlow::SourceNode trackUseNode( + DataFlow::SourceNode nd, boolean promisified, int boundArgs, string prop + ) { + result = trackUseNode(nd, promisified, boundArgs, prop, DataFlow::TypeTracker::end()) + } + + /** + * Gets a node that is inter-procedurally reachable from `nd`, which is a use of some node. + */ + DataFlow::SourceNode trackUseNode(DataFlow::SourceNode nd) { + result = trackUseNode(nd, false, 0, "") + } + + /** + * Gets a node whose forward tracking reaches `nd` in some state (e.g. possibly inside a content at this point). + */ + DataFlow::SourceNode trackUseNodeAnyState(DataFlow::SourceNode nd) { + result = trackUseNode(nd, _, _, _, _) + } + + private DataFlow::SourceNode trackDefNode(DataFlow::Node nd, DataFlow::TypeBackTracker t) { + t.start() and + rhs(_, nd) and + result = nd.getALocalSource() + or + // additional backwards step from `require('m')` to `exports` or `module.exports` in m + exists(Import imp | imp.getImportedModuleNodeStrict() = trackDefNode(nd, t.continue()) | + result = DataFlow::exportsVarNode(imp.getImportedModule()) + or + result = DataFlow::moduleVarNode(imp.getImportedModule()).getAPropertyRead("exports") + ) or - decoratorUseEdge(base, lbl, ref) + exists(ObjectExpr obj | + obj = trackDefNode(nd, t.continue()).asExpr() and + result = + obj.getAProperty() + .(SpreadProperty) + .getInit() + .(SpreadElement) + .getOperand() + .flow() + .getALocalSource() + ) or - // for fields and accessors, mark the reads as use-nodes - decoratorPropEdge(base, lbl, ref.(DataFlow::PropRead)) - ) - } + t = defStep(nd, result) + } - /** Holds if `base` is a use-node that flows to the decorator expression of the given decorator. */ - pragma[nomagic] - private predicate useNodeFlowsToDecorator(TApiNode base, Decorator decorator) { - exists(DataFlow::SourceNode decoratorSrc | - use(base, decoratorSrc) and - trackUseNode(decoratorSrc).flowsToExpr(decorator.getExpression()) - ) - } + /** + * Holds if `nd`, which is a def of an API-graph node, can be reached in zero or more potentially + * inter-procedural steps from some intermediate node, and `prev` flows into that intermediate node + * in one step. The entire flow is described by the resulting `TypeTracker`. + * + * This predicate exists solely to enforce a better join order in `trackDefNode` above. + */ + pragma[noopt] + private DataFlow::TypeBackTracker defStep(DataFlow::Node nd, DataFlow::SourceNode prev) { + exists(DataFlow::TypeBackTracker t, StepSummary summary, DataFlow::Node next | + next = trackDefNode(nd, t) and + StepSummary::step(prev, next, summary) and + result = t.prepend(summary) and + // Block argument-passing into 'this' when it determines the call target + not summary = CallReceiverStep() + ) + } - /** - * Holds if `ref` corresponds to both a use and def-node that should have an incoming edge from `base` labelled `lbl`. - * - * This happens because the decorated value escapes into the decorator function, and is then replaced - * by the function's return value. In the JS analysis we generally assume decorators return their input, - * but library models may want to find the return value. - */ - private predicate decoratorDualEdge(TApiNode base, Label::ApiLabel lbl, DataFlow::Node ref) { - exists(ClassDefinition cls | - useNodeFlowsToDecorator(base, cls.getADecorator()) and - lbl = Label::decoratedClass() and - ref = DataFlow::valueNode(cls) - ) - or - exists(MethodDefinition method | - useNodeFlowsToDecorator(base, method.getADecorator()) and - not method instanceof AccessorMethodDefinition and - lbl = Label::decoratedMember() and - ref = DataFlow::valueNode(method.getBody()) - ) - } + /** + * Gets a node that inter-procedurally flows into `nd`, which is a definition of some node. + */ + DataFlow::SourceNode trackDefNode(DataFlow::Node nd) { + result = trackDefNode(nd, DataFlow::TypeBackTracker::end()) + } - /** Holds if `ref` is a use that should have an incoming edge from `base` labelled `lbl`, induced by a decorator. */ - private predicate decoratorUseEdge(TApiNode base, Label::ApiLabel lbl, DataFlow::Node ref) { - exists(SetterMethodDefinition accessor | - useNodeFlowsToDecorator(base, - [accessor.getADecorator(), accessor.getCorrespondingGetter().getADecorator()]) and - lbl = Label::decoratedMember() and - ref = DataFlow::parameterNode(accessor.getBody().getParameter(0)) - ) - or - exists(Parameter param | - useNodeFlowsToDecorator(base, param.getADecorator()) and - lbl = Label::decoratedParameter() and - ref = DataFlow::parameterNode(param) - ) - } + /** + * Gets a node reached by the backwards tracking of `nd` in some state (e.g. possibly inside a content at this point). + */ + DataFlow::SourceNode trackDefNodeAnyState(DataFlow::Node nd) { result = trackDefNode(nd, _) } - /** Holds if `rhs` is a def node that should have an incoming edge from `base` labelled `lbl`, induced by a decorator. */ - private predicate decoratorRhsEdge(TApiNode base, Label::ApiLabel lbl, DataFlow::Node rhs) { - exists(GetterMethodDefinition accessor | - useNodeFlowsToDecorator(base, - [accessor.getADecorator(), accessor.getCorrespondingSetter().getADecorator()]) and - lbl = Label::decoratedMember() and - rhs = DataFlow::valueNode(accessor.getBody().getAReturnedExpr()) - ) - } + private DataFlow::SourceNode awaited(DataFlow::InvokeNode call, DataFlow::TypeTracker t) { + t.startInPromise() and + trackUseNode(_, true, _, "").flowsTo(call.getCalleeNode()) and + result = call + or + exists(DataFlow::TypeTracker t2 | result = awaited(call, t2).track(t2, t)) + } - /** - * Holds if `ref` is a reference to a field/accessor that should have en incoming edge from base labelled `lbl`. - * - * Since fields do not have their own data-flow nodes, we generate a node for each read or write. - * For property writes, the right-hand side becomes a def-node and property reads become use-nodes. - * - * For accessors this predicate computes each use of the accessor. - * The return value inside the accessor is computed by the `decoratorRhsEdge` predicate. - */ - private predicate decoratorPropEdge(TApiNode base, Label::ApiLabel lbl, DataFlow::PropRef ref) { - exists(MemberDefinition fieldLike, DataFlow::ClassNode cls | - fieldLike instanceof FieldDefinition + /** + * Gets a node holding the resolved value of promise `call`. + */ + private DataFlow::Node awaited(DataFlow::InvokeNode call) { + result = awaited(call, DataFlow::TypeTracker::end()) + } + + /** + * Holds if there is an edge from `pred` to `succ` in the API graph that is labeled with `lbl`. + */ + predicate edge(TApiNode pred, Label::ApiLabel lbl, TApiNode succ) { + Stages::ApiStage::ref() and + exists(string m | + pred = MkRoot() and + lbl = Label::moduleLabel(m) + | + succ = MkModuleDef(m) + or + succ = MkModuleUse(m) + ) or - fieldLike instanceof AccessorMethodDefinition - | - useNodeFlowsToDecorator(base, fieldLike.getADecorator()) and - lbl = Label::decoratedMember() and - cls = fieldLike.getDeclaringClass().flow() and - ( - fieldLike.isStatic() and - ref = cls.getAClassReference().getAPropertyReference(fieldLike.getName()) + exists(string m | + pred = MkModuleDef(m) and + lbl = Label::member("exports") and + succ = MkModuleExport(m) or - not fieldLike.isStatic() and - ref = cls.getAnInstanceReference().getAPropertyReference(fieldLike.getName()) + pred = MkModuleUse(m) and + lbl = Label::member("exports") and + succ = MkModuleImport(m) ) - ) - } - - /** - * Holds if `ref` is a use of node `nd`. - */ - cached - predicate use(TApiNode nd, DataFlow::Node ref) { - exists(string m, Module mod | nd = MkModuleDef(m) and mod = importableModule(m) | - ref = DataFlow::moduleVarNode(mod) - ) - or - exists(string m, Module mod | nd = MkModuleExport(m) and mod = importableModule(m) | - ref = DataFlow::exportsVarNode(mod) or - exists(DataFlow::Node base | use(MkModuleDef(m), base) | - ref = trackUseNode(base).getAPropertyRead("exports") + exists(DataFlow::SourceNode ref | + use(pred, lbl, ref) and + succ = MkUse(ref) ) - ) - or - exists(string m | - nd = MkModuleImport(m) and - ref = DataFlow::moduleImport(m) - ) - or - exists(DataFlow::ClassNode cls | nd = MkClassInstance(cls) | - ref = cls.getAReceiverNode() or - ref = cls.(DataFlow::ClassNode).getAPrototypeReference() - ) - or - nd = MkUse(ref) + exists(DataFlow::Node rhs | rhs(pred, lbl, rhs) | + succ = MkDef(rhs) + or + exists(DataFlow::ClassNode cls | + needsDefNode(cls) and + cls.getAnInstanceReference().flowsTo(rhs) and + succ = MkClassInstance(cls) + ) + ) + or + exists(DataFlow::Node def | + rhs(pred, def) and + lbl = Label::instance() and + succ = MkClassInstance(trackDefNode(def)) + ) + or + exists(string moduleName, string exportName | + pred = MkModuleImport(moduleName) and + lbl = Label::member(exportName) and + succ = MkTypeUse(moduleName, exportName) + ) + or + exists(DataFlow::Node nd, DataFlow::FunctionNode f | + f.getFunction().isAsync() and + pred = MkDef(nd) and + f = trackDefNode(nd) and + lbl = Label::return() and + succ = MkDef(f.getReturnNode()) + ) + or + exists(int bound, DataFlow::InvokeNode call | + lbl = Label::parameter(bound + call.getNumArgument()) and + call = getAPromisifiedInvocation(pred, bound, succ) + ) + } + + /** + * Gets a call to a promisified function represented by `callee` where + * `bound` arguments have been bound. + */ + DataFlow::InvokeNode getAPromisifiedInvocation(TApiNode callee, int bound, TApiNode succ) { + exists(DataFlow::SourceNode src | + use(callee, src) and + trackUseNode(src, true, bound, "").flowsTo(result.getCalleeNode()) and + succ = Impl::MkSyntheticCallbackArg(result) + ) + } } - private import semmle.javascript.dataflow.TypeTracking + private module Stage1Input implements StageInputSig { + overlay[caller] + pragma[inline] + predicate isAdditionalUseRoot(Node node) { none() } - /** - * Gets a data-flow node to which `nd`, which is a use of an API-graph node, flows. - * - * The flow from `nd` to that node may be inter-procedural, and is further described by three - * flags: - * - * - `promisified`: if true `true`, the flow goes through a promisification; - * - `boundArgs`: for function values, tracks how many arguments have been bound throughout - * the flow. To ensure termination, we somewhat arbitrarily constrain the number of bound - * arguments to be at most ten. - * - `prop`: if non-empty, the flow is only guaranteed to preserve the value of this property, - * and not necessarily the entire object. - */ - private DataFlow::SourceNode trackUseNode( - DataFlow::SourceNode nd, boolean promisified, int boundArgs, string prop, - DataFlow::TypeTracker t - ) { - t.start() and - use(_, nd) and - result = nd and - promisified = false and - boundArgs = 0 and - prop = "" - or - exists(Promisify::PromisifyCall promisify | - trackUseNode(nd, false, boundArgs, prop, t.continue()).flowsTo(promisify.getArgument(0)) and - promisified = true and - prop = "" and - result = promisify - ) - or - exists(DataFlow::PartialInvokeNode pin, DataFlow::Node pred, int predBoundArgs | - trackUseNode(nd, promisified, predBoundArgs, prop, t.continue()).flowsTo(pred) and - prop = "" and - result = pin.getBoundFunction(pred, boundArgs - predBoundArgs) and - boundArgs in [0 .. 10] - ) - or - exists(DataFlow::SourceNode mid | - mid = trackUseNode(nd, promisified, boundArgs, prop, t) and - AdditionalUseStep::step(pragma[only_bind_out](mid), result) - ) - or - exists(DataFlow::Node pred, string preprop | - trackUseNode(nd, promisified, boundArgs, preprop, t.continue()).flowsTo(pred) and - promisified = false and - boundArgs = 0 and - SharedTypeTrackingStep::loadStoreStep(pred, result, prop) - | - prop = preprop - or - preprop = "" - ) - or - t = useStep(nd, promisified, boundArgs, prop, result) - } + overlay[caller] + pragma[inline] + predicate isAdditionalDefRoot(Node node) { none() } - /** - * Holds if `nd`, which is a use of an API-graph node, flows in zero or more potentially - * inter-procedural steps to some intermediate node, and then from that intermediate node to - * `res` in one step. The entire flow is described by the resulting `TypeTracker`. - * - * This predicate exists solely to enforce a better join order in `trackUseNode` above. - */ - pragma[noopt] - private DataFlow::TypeTracker useStep( - DataFlow::Node nd, boolean promisified, int boundArgs, string prop, DataFlow::Node res - ) { - exists(DataFlow::TypeTracker t, StepSummary summary, DataFlow::SourceNode prev | - prev = trackUseNode(nd, promisified, boundArgs, prop, t) and - StepSummary::step(prev, res, summary) and - result = t.append(summary) and - // Block argument-passing into 'this' when it determines the call target - not summary = CallReceiverStep() - ) - } + overlay[local] + private predicate isOverlay() { databaseMetadata("isOverlay", "true") } - private DataFlow::SourceNode trackUseNode( - DataFlow::SourceNode nd, boolean promisified, int boundArgs, string prop - ) { - result = trackUseNode(nd, promisified, boundArgs, prop, DataFlow::TypeTracker::end()) + bindingset[node] + predicate inActiveFile(DataFlow::Node node) { + // In the base database, compute everything in stage 1. + // In an overlay database, do nothing in stage 1. + not isOverlay() and exists(node) + } } - /** - * Gets a node that is inter-procedurally reachable from `nd`, which is a use of some node. - */ - cached - DataFlow::SourceNode trackUseNode(DataFlow::SourceNode nd) { - result = trackUseNode(nd, false, 0, "") - } + private module Stage1 = Stage; - private DataFlow::SourceNode trackDefNode(DataFlow::Node nd, DataFlow::TypeBackTracker t) { - t.start() and - rhs(_, nd) and - result = nd.getALocalSource() - or - // additional backwards step from `require('m')` to `exports` or `module.exports` in m - exists(Import imp | imp.getImportedModuleNodeStrict() = trackDefNode(nd, t.continue()) | - result = DataFlow::exportsVarNode(imp.getImportedModule()) - or - result = DataFlow::moduleVarNode(imp.getImportedModule()).getAPropertyRead("exports") - ) - or - exists(ObjectExpr obj | - obj = trackDefNode(nd, t.continue()).asExpr() and - result = - obj.getAProperty() - .(SpreadProperty) - .getInit() - .(SpreadElement) - .getOperand() - .flow() - .getALocalSource() - ) - or - t = defStep(nd, result) - } + overlay[local] + private module Stage1Local { + predicate use(TApiNode node, DataFlow::Node ref) = forceLocal(Stage1::use/2)(node, ref) - /** - * Holds if `nd`, which is a def of an API-graph node, can be reached in zero or more potentially - * inter-procedural steps from some intermediate node, and `prev` flows into that intermediate node - * in one step. The entire flow is described by the resulting `TypeTracker`. - * - * This predicate exists solely to enforce a better join order in `trackDefNode` above. - */ - pragma[noopt] - private DataFlow::TypeBackTracker defStep(DataFlow::Node nd, DataFlow::SourceNode prev) { - exists(DataFlow::TypeBackTracker t, StepSummary summary, DataFlow::Node next | - next = trackDefNode(nd, t) and - StepSummary::step(prev, next, summary) and - result = t.prepend(summary) and - // Block argument-passing steps from 'this' back to a receiver when it determines the call target - not summary = CallReceiverStep() - ) - } + predicate rhs(TApiNode node, DataFlow::Node def) = forceLocal(Stage1::rhs/2)(node, def) - /** - * Gets a node that inter-procedurally flows into `nd`, which is a definition of some node. - */ - cached - DataFlow::SourceNode trackDefNode(DataFlow::Node nd) { - result = trackDefNode(nd, DataFlow::TypeBackTracker::end()) - } + DataFlow::SourceNode trackUseNode(DataFlow::SourceNode nd) = + forceLocal(Stage1::trackUseNode/1)(nd, result) - private DataFlow::SourceNode awaited(DataFlow::InvokeNode call, DataFlow::TypeTracker t) { - t.startInPromise() and - exists(MkSyntheticCallbackArg(_, _, call)) and - result = call - or - exists(DataFlow::TypeTracker t2 | result = awaited(call, t2).track(t2, t)) + DataFlow::SourceNode trackUseNodeAnyState(DataFlow::SourceNode nd) = + forceLocal(Stage1::trackUseNodeAnyState/1)(nd, result) + + DataFlow::SourceNode trackDefNode(DataFlow::Node nd) = + forceLocal(Stage1::trackDefNode/1)(nd, result) + + DataFlow::SourceNode trackDefNodeAnyState(DataFlow::Node nd) = + forceLocal(Stage1::trackDefNodeAnyState/1)(nd, result) + + predicate edge(TApiNode pred, Label::ApiLabel lbl, TApiNode succ) = + forceLocal(Stage1::edge/3)(pred, lbl, succ) + + DataFlow::InvokeNode getAPromisifiedInvocation(TApiNode callee, int bound, TApiNode succ) = + forceLocal(Stage1::getAPromisifiedInvocation/3)(callee, bound, succ, result) } - /** - * Gets a node holding the resolved value of promise `call`. - */ - private DataFlow::Node awaited(DataFlow::InvokeNode call) { - result = awaited(call, DataFlow::TypeTracker::end()) + private module Stage2Input implements StageInputSig { + overlay[global] + pragma[nomagic] + private predicate isInOverlayChangedFile(DataFlow::Node node) { + overlayChangedFiles(node.getFile().getAbsolutePath()) + } + + bindingset[node] + overlay[global] + pragma[inline_late] + private predicate isInOverlayChangedFileLate(DataFlow::Node node) { + isInOverlayChangedFile(node) + } + + /** Holds if there is a step `node1 -> node2` from an unchanged file into a changed file. */ + pragma[nomagic] + private predicate stepIntoOverlay(DataFlow::Node node1, DataFlow::Node node2) { + StepSummary::step(node1, node2, _) and + isInOverlayChangedFile(node2) and + not isInOverlayChangedFileLate(node1) and + hasSemanticsLate(node1) + } + + /** Holds if use-node tracking starting at `nd` can reach a node in the overlay. */ + pragma[nomagic] + private predicate shouldTrackIntoOverlay(DataFlow::SourceNode nd) { + exists(DataFlow::Node overlayNode | + stepIntoOverlay(Stage1Local::trackUseNodeAnyState(nd), overlayNode) + ) + } + + /** Holds if `node` should be tracked as a use-node in stage 2. */ + pragma[nomagic] + predicate isAdditionalUseRoot(Node node) { + exists(DataFlow::Node ref | + shouldTrackIntoOverlay(ref) and + Stage1Local::use(node, ref) + ) + } + + /** Holds if there is a step `node1 -> node2` from a changed file into an unchanged file. */ + pragma[nomagic] + private predicate stepOutOfOverlay(DataFlow::Node node1, DataFlow::Node node2) { + StepSummary::step(node1, node2, _) and + isInOverlayChangedFile(node1) and + not isInOverlayChangedFileLate(node2) and + hasSemanticsLate(node2) + } + + /** Holds if def-node tracking starting at `nd` can reach a node in the overlay. */ + pragma[nomagic] + private predicate shouldBacktrackIntoOverlay(DataFlow::Node nd) { + exists(DataFlow::Node overlayNode | + stepOutOfOverlay(overlayNode, Stage1Local::trackDefNodeAnyState(nd)) + ) + } + + /** Holds if `node` should be tracked as a def-node in stage 2. */ + pragma[nomagic] + predicate isAdditionalDefRoot(Node node) { + exists(DataFlow::Node def | + shouldBacktrackIntoOverlay(def) and + Stage1Local::rhs(node, def) + ) + } + + bindingset[node] + predicate inActiveFile(DataFlow::Node node) { isInOverlayChangedFile(node) } } - /** - * Holds if there is an edge from `pred` to `succ` in the API graph that is labeled with `lbl`. - */ + private module Stage2 = Stage; + cached - predicate edge(TApiNode pred, Label::ApiLabel lbl, TApiNode succ) { - Stages::ApiStage::ref() and - exists(string m | - pred = MkRoot() and - lbl = Label::moduleLabel(m) - | - succ = MkModuleDef(m) + private module Cached { + cached + predicate rhs(TApiNode nd, DataFlow::Node rhs) { + Stage1Local::rhs(nd, rhs) or - succ = MkModuleUse(m) - ) - or - exists(string m | - pred = MkModuleDef(m) and - lbl = Label::member("exports") and - succ = MkModuleExport(m) + Stage2::rhs(nd, rhs) + } + + cached + predicate use(TApiNode nd, DataFlow::Node ref) { + Stage1Local::use(nd, ref) or - pred = MkModuleUse(m) and - lbl = Label::member("exports") and - succ = MkModuleImport(m) - ) - or - exists(DataFlow::SourceNode ref | - use(pred, lbl, ref) and - succ = MkUse(ref) - ) - or - exists(DataFlow::Node rhs | rhs(pred, lbl, rhs) | - succ = MkDef(rhs) + Stage2::use(nd, ref) + } + + cached + DataFlow::SourceNode trackUseNode(DataFlow::SourceNode nd) { + result = Stage1Local::trackUseNode(nd) or - exists(DataFlow::ClassNode cls | - cls.getAnInstanceReference().flowsTo(rhs) and - succ = MkClassInstance(cls) - ) - ) - or - exists(DataFlow::Node def | - rhs(pred, def) and - lbl = Label::instance() and - succ = MkClassInstance(trackDefNode(def)) - ) - or - exists(string moduleName, string exportName | - pred = MkModuleImport(moduleName) and - lbl = Label::member(exportName) and - succ = MkTypeUse(moduleName, exportName) - ) - or - exists(DataFlow::Node nd, DataFlow::FunctionNode f | - f.getFunction().isAsync() and - pred = MkDef(nd) and - f = trackDefNode(nd) and - lbl = Label::return() and - succ = MkDef(f.getReturnNode()) - ) - or - exists(int bound, DataFlow::InvokeNode call | - lbl = Label::parameter(bound + call.getNumArgument()) and - call = getAPromisifiedInvocation(pred, bound, succ) - ) + result = Stage2::trackUseNode(nd) + } + + cached + DataFlow::SourceNode trackDefNode(DataFlow::Node nd) { + result = Stage1Local::trackDefNode(nd) + or + result = Stage2::trackDefNode(nd) + } + + cached + predicate edge(Node pred, Label::ApiLabel lbl, Node succ) { + Stage1Local::edge(pred, lbl, succ) + or + Stage2::edge(pred, lbl, succ) + } + + cached + DataFlow::InvokeNode getAPromisifiedInvocation(TApiNode callee, int bound, TApiNode succ) { + result = Stage1Local::getAPromisifiedInvocation(callee, bound, succ) + or + result = Stage2::getAPromisifiedInvocation(callee, bound, succ) + } + } + + import Cached + + private module Debug { + private module FullInput implements StageInputSig { + overlay[caller] + pragma[inline] + predicate isAdditionalUseRoot(Node node) { none() } + + overlay[caller] + pragma[inline] + predicate isAdditionalDefRoot(Node node) { none() } + + bindingset[node] + predicate inActiveFile(DataFlow::Node node) { any() } + } + + private module Full = Stage; + + query predicate missingDefNode(DataFlow::Node node) { + Full::rhs(_, _, node) and + not exists(MkDef(node)) + } + + query predicate missingUseNode(DataFlow::Node node) { + Full::use(_, _, node) and + not exists(MkUse(node)) + } + + query predicate lostEdge(Node pred, Label::ApiLabel lbl, Node succ) { + Full::edge(pred, lbl, succ) and + not Cached::edge(pred, lbl, succ) + } + + query predicate counts(int numEdges, int numOverlayEdges, float ratio) { + numEdges = count(Node pred, Label::ApiLabel lbl, Node succ | Full::edge(pred, lbl, succ)) and + numOverlayEdges = + count(Node pred, Label::ApiLabel lbl, Node succ | Stage2::edge(pred, lbl, succ)) and + ratio = numOverlayEdges / numEdges.(float) + } } /** @@ -1485,18 +1791,6 @@ module API { /** Gets the shortest distance from the root to `nd` in the API graph. */ cached int distanceFromRoot(TApiNode nd) = shortestDistances(MkRoot/0, edge/2)(_, nd, result) - - /** - * Gets a call to a promisified function represented by `callee` where - * `bound` arguments have been bound. - */ - cached - DataFlow::InvokeNode getAPromisifiedInvocation(TApiNode callee, int bound, TApiNode succ) { - exists(DataFlow::SourceNode src | - Impl::use(callee, src) and - succ = Impl::MkSyntheticCallbackArg(src, bound, result) - ) - } } /** @@ -1556,6 +1850,7 @@ module API { class NewNode extends InvokeNode, DataFlow::NewNode { } /** Provides classes modeling the various edges (labels) in the API graph. */ + overlay[local] module Label { /** A label in the API-graph */ class ApiLabel extends TLabel { @@ -1594,6 +1889,7 @@ module API { * This is to support code patterns where the property name is actually constant, * but the property name has been factored into a library. */ + overlay[global] private string getAnIndirectPropName(DataFlow::PropRef ref) { exists(DataFlow::Node pred | FlowSteps::propertyFlowStep(pred, ref.getPropertyNameExpr().flow()) and @@ -1604,16 +1900,19 @@ module API { /** * Gets unique result of `getAnIndirectPropName` if there is one. */ + overlay[global] private string getIndirectPropName(DataFlow::PropRef ref) { result = unique(string s | s = getAnIndirectPropName(ref)) } + overlay[global] pragma[nomagic] private predicate isEnumeratedPropName(DataFlow::Node node) { node.getAPredecessor*() instanceof EnumeratedPropName } /** Gets the `member` edge label for the given property reference. */ + overlay[global] ApiLabel memberFromRef(DataFlow::PropRef pr) { exists(string pn | pn = pr.getPropertyName() or pn = getIndirectPropName(pr) | result = member(pn) and @@ -1680,9 +1979,17 @@ module API { MkLabelInstance() or MkLabelContent(DataFlow::Content content) or MkLabelMember(string name) { - name instanceof PropertyName + name instanceof ContentPrivate::PropertyName + or + name = any(DataFlow::PropRef pr).getPropertyName() + or + exists(AccessPath::getAnAssignmentTo(_, name)) + or + name = DataFlow::PseudoProperties::arrayLikeElement() or - exists(Impl::MkTypeUse(_, name)) + name = any(TypeAccess t).getIdentifier().getName() + or + name = any(Expr s).getStringValue() } or MkLabelParameter(int i) { i = @@ -1885,3 +2192,18 @@ private Module importableModule(string m) { m = pkg.getPackageName() ) } + +overlay[local] +private predicate isDeclaredPackageName(string m) { + m = any(PackageJson pkg).getDeclaredPackageName() +} + +overlay[local] +private predicate isImportedPackageName(string m) { + ( + m = any(Import imprt).getImportedPathString() + or + m = any(DataFlow::ModuleImportNode im).getPath() + ) and + m.regexpMatch("[^./].*") +} diff --git a/javascript/ql/lib/semmle/javascript/DOM.qll b/javascript/ql/lib/semmle/javascript/DOM.qll index 21854c1a9d0e..97f6bc435464 100644 --- a/javascript/ql/lib/semmle/javascript/DOM.qll +++ b/javascript/ql/lib/semmle/javascript/DOM.qll @@ -192,6 +192,7 @@ module DOM { * A data flow node or other program element that may refer to * a DOM element. */ + overlay[global] abstract class Element extends Locatable { ElementDefinition defn; diff --git a/javascript/ql/lib/semmle/javascript/ES2015Modules.qll b/javascript/ql/lib/semmle/javascript/ES2015Modules.qll index 9f37e3082b89..4959a8c3b041 100644 --- a/javascript/ql/lib/semmle/javascript/ES2015Modules.qll +++ b/javascript/ql/lib/semmle/javascript/ES2015Modules.qll @@ -29,6 +29,7 @@ class ES2015Module extends Module { override string getName() { result = this.getFile().getStem() } /** Gets an export declaration in this module. */ + pragma[nomagic] ExportDeclaration getAnExport() { result.getTopLevel() = this } overlay[global] @@ -38,6 +39,7 @@ class ES2015Module extends Module { /** Holds if this module exports variable `v` under the name `name`. */ overlay[global] + pragma[nomagic] predicate exportsAs(LexicalName v, string name) { this.getAnExport().exportsAs(v, name) } override predicate isStrict() { @@ -345,6 +347,7 @@ abstract class ExportDeclaration extends Stmt, @export_declaration { /** Holds if this export declaration exports variable `v` under the name `name`. */ overlay[global] + pragma[nomagic] final predicate exportsAs(LexicalName v, string name) { this.exportsDirectlyAs(v, name) or @@ -821,18 +824,31 @@ class SelectiveReExportDeclaration extends ReExportDeclaration, ExportNamedDecla result = ExportNamedDeclaration.super.getImportedPath() } + overlay[global] + pragma[nomagic] + private predicate reExportsFrom(ES2015Module mod, string originalName, string reExportedName) { + exists(ExportSpecifier spec | + spec = this.getASpecifier() and + reExportedName = spec.getExportedName() and + originalName = spec.getLocalName() and + mod = this.getReExportedES2015Module() + ) + } + overlay[global] override predicate reExportsAs(LexicalName v, string name) { - exists(ExportSpecifier spec | spec = this.getASpecifier() and name = spec.getExportedName() | - this.getReExportedES2015Module().exportsAs(v, spec.getLocalName()) + exists(ES2015Module mod, string originalName | + this.reExportsFrom(mod, originalName, name) and + mod.exportsAs(v, originalName) ) and not (this.isTypeOnly() and v instanceof Variable) } overlay[global] override DataFlow::Node getReExportedSourceNode(string name) { - exists(ExportSpecifier spec | spec = this.getASpecifier() and name = spec.getExportedName() | - result = this.getReExportedES2015Module().getAnExport().getSourceNode(spec.getLocalName()) + exists(ES2015Module mod, string originalName | + this.reExportsFrom(mod, originalName, name) and + result = mod.getAnExport().getSourceNode(originalName) ) } } diff --git a/javascript/ql/lib/semmle/javascript/frameworks/ClientRequests.qll b/javascript/ql/lib/semmle/javascript/frameworks/ClientRequests.qll index 22db9f24b99e..9da93400ef92 100644 --- a/javascript/ql/lib/semmle/javascript/frameworks/ClientRequests.qll +++ b/javascript/ql/lib/semmle/javascript/frameworks/ClientRequests.qll @@ -198,6 +198,7 @@ module ClientRequest { private string urlPropertyName() { result = "url" or result = "uri" } /** An API entry-point for the global variable `axios`. */ + overlay[local?] private class AxiosGlobalEntryPoint extends API::EntryPoint { AxiosGlobalEntryPoint() { this = "axiosGlobal" } diff --git a/javascript/ql/lib/semmle/javascript/frameworks/D3.qll b/javascript/ql/lib/semmle/javascript/frameworks/D3.qll index cc7c07c80c19..138e3b05d576 100644 --- a/javascript/ql/lib/semmle/javascript/frameworks/D3.qll +++ b/javascript/ql/lib/semmle/javascript/frameworks/D3.qll @@ -6,6 +6,7 @@ private import semmle.javascript.security.dataflow.DomBasedXssCustomizations /** Provides classes and predicates modeling aspects of the `d3` library. */ module D3 { /** The global variable `d3` as an entry point for API graphs. */ + overlay[local?] private class D3GlobalEntry extends API::EntryPoint { D3GlobalEntry() { this = "D3GlobalEntry" } diff --git a/javascript/ql/lib/semmle/javascript/frameworks/Electron.qll b/javascript/ql/lib/semmle/javascript/frameworks/Electron.qll index 796770b96ee0..2d21baac1f98 100644 --- a/javascript/ql/lib/semmle/javascript/frameworks/Electron.qll +++ b/javascript/ql/lib/semmle/javascript/frameworks/Electron.qll @@ -41,6 +41,7 @@ module Electron { BrowserView() { this = DataFlow::moduleMember("electron", "BrowserView").getAnInstantiation() } } + overlay[local?] private class ElectronEntryPoint extends API::EntryPoint { ElectronEntryPoint() { this = "Electron.Browser" } diff --git a/javascript/ql/lib/semmle/javascript/frameworks/EventEmitter.qll b/javascript/ql/lib/semmle/javascript/frameworks/EventEmitter.qll index f466d96dd9de..67a5a99fdc11 100644 --- a/javascript/ql/lib/semmle/javascript/frameworks/EventEmitter.qll +++ b/javascript/ql/lib/semmle/javascript/frameworks/EventEmitter.qll @@ -94,6 +94,7 @@ module EventRegistration { /** * A registration of an event handler on an EventEmitter. */ + overlay[global] abstract class Range extends DataFlow::Node { EventEmitter::Range emitter; @@ -148,6 +149,7 @@ module EventDispatch { /** * A dispatch of an event on an EventEmitter. */ + overlay[global] abstract class Range extends DataFlow::Node { EventEmitter::Range emitter; diff --git a/javascript/ql/lib/semmle/javascript/frameworks/History.qll b/javascript/ql/lib/semmle/javascript/frameworks/History.qll index 37c0057f6c1f..224eb2b4b595 100644 --- a/javascript/ql/lib/semmle/javascript/frameworks/History.qll +++ b/javascript/ql/lib/semmle/javascript/frameworks/History.qll @@ -5,6 +5,7 @@ import javascript /** Provides classes modeling the [`history`](https://npmjs.org/package/history) library. */ module History { /** The global variable `HistoryLibrary` as an entry point for API graphs. */ + overlay[local?] private class HistoryGlobalEntry extends API::EntryPoint { HistoryGlobalEntry() { this = "HistoryLibrary" } diff --git a/javascript/ql/lib/semmle/javascript/frameworks/Immutable.qll b/javascript/ql/lib/semmle/javascript/frameworks/Immutable.qll index 1adaed5b4398..9a94fc26341c 100644 --- a/javascript/ql/lib/semmle/javascript/frameworks/Immutable.qll +++ b/javascript/ql/lib/semmle/javascript/frameworks/Immutable.qll @@ -13,6 +13,7 @@ private module Immutable { /** * An API entrypoint for the global `Immutable` variable. */ + overlay[local?] private class ImmutableGlobalEntry extends API::EntryPoint { ImmutableGlobalEntry() { this = "ImmutableGlobalEntry" } diff --git a/javascript/ql/lib/semmle/javascript/frameworks/Logging.qll b/javascript/ql/lib/semmle/javascript/frameworks/Logging.qll index aa0151595dfd..e297dbd7afde 100644 --- a/javascript/ql/lib/semmle/javascript/frameworks/Logging.qll +++ b/javascript/ql/lib/semmle/javascript/frameworks/Logging.qll @@ -32,6 +32,7 @@ private module Console { /** * An API entrypoint for the global `console` variable. */ + overlay[local?] private class ConsoleGlobalEntry extends API::EntryPoint { ConsoleGlobalEntry() { this = "ConsoleGlobalEntry" } diff --git a/javascript/ql/lib/semmle/javascript/frameworks/Nest.qll b/javascript/ql/lib/semmle/javascript/frameworks/Nest.qll index d7474aae8ca4..4c32f70b9816 100644 --- a/javascript/ql/lib/semmle/javascript/frameworks/Nest.qll +++ b/javascript/ql/lib/semmle/javascript/frameworks/Nest.qll @@ -140,6 +140,7 @@ module NestJS { } /** API node entry point for custom implementations of `ValidationPipe` (a common pattern). */ + overlay[local?] private class ValidationNodeEntry extends API::EntryPoint { ValidationNodeEntry() { this = "ValidationNodeEntry" } diff --git a/javascript/ql/lib/semmle/javascript/frameworks/NodeJSLib.qll b/javascript/ql/lib/semmle/javascript/frameworks/NodeJSLib.qll index 89d436bb64c7..5ea54cbb8c0f 100644 --- a/javascript/ql/lib/semmle/javascript/frameworks/NodeJSLib.qll +++ b/javascript/ql/lib/semmle/javascript/frameworks/NodeJSLib.qll @@ -260,6 +260,7 @@ module NodeJSLib { DataFlow::Node getRouteHandlerNode() { result = handler } } + overlay[global] abstract private class HeaderDefinition extends Http::Servers::StandardHeaderDefinition { ResponseNode r; diff --git a/javascript/ql/lib/semmle/javascript/frameworks/Redux.qll b/javascript/ql/lib/semmle/javascript/frameworks/Redux.qll index 78931da585a4..3aaf07f637d9 100644 --- a/javascript/ql/lib/semmle/javascript/frameworks/Redux.qll +++ b/javascript/ql/lib/semmle/javascript/frameworks/Redux.qll @@ -1099,6 +1099,7 @@ module Redux { * Used to catch cases where the `connect` function was not recognized by API graphs (usually because of it being * wrapped in another function, which API graphs won't look through). */ + overlay[local?] private class HeuristicConnectEntryPoint extends API::EntryPoint { HeuristicConnectEntryPoint() { this = "react-redux-connect" } diff --git a/javascript/ql/lib/semmle/javascript/frameworks/SQL.qll b/javascript/ql/lib/semmle/javascript/frameworks/SQL.qll index 9d106251a211..bcc898132200 100644 --- a/javascript/ql/lib/semmle/javascript/frameworks/SQL.qll +++ b/javascript/ql/lib/semmle/javascript/frameworks/SQL.qll @@ -16,6 +16,7 @@ module SQL { * An dataflow node that sanitizes a string to make it safe to embed into * a SQL command. */ + overlay[global] abstract class SqlSanitizer extends DataFlow::Node { DataFlow::Node input; DataFlow::Node output; diff --git a/javascript/ql/lib/semmle/javascript/frameworks/Templating.qll b/javascript/ql/lib/semmle/javascript/frameworks/Templating.qll index f1f91785329c..802b62625cfd 100644 --- a/javascript/ql/lib/semmle/javascript/frameworks/Templating.qll +++ b/javascript/ql/lib/semmle/javascript/frameworks/Templating.qll @@ -33,7 +33,7 @@ module Templating { */ bindingset[prefix] string getDelimiterMatchingRegexpWithPrefix(string prefix) { - result = "(?s)" + prefix + "(" + concat("\\Q" + getADelimiter() + "\\E", "|") + ").*" + result = "(?s)" + prefix + "(" + strictconcat("\\Q" + getADelimiter() + "\\E", "|") + ").*" } /** A placeholder tag for a templating engine. */ @@ -703,7 +703,7 @@ module Templating { * * These API nodes are used in the `getTemplateInput` predicate. */ - overlay[global] + overlay[local?] private class IncludeFunctionAsEntryPoint extends API::EntryPoint { IncludeFunctionAsEntryPoint() { this = "IncludeFunctionAsEntryPoint" } diff --git a/javascript/ql/lib/semmle/javascript/frameworks/TrustedTypes.qll b/javascript/ql/lib/semmle/javascript/frameworks/TrustedTypes.qll index ca9de4e481fa..8d32c976c57d 100644 --- a/javascript/ql/lib/semmle/javascript/frameworks/TrustedTypes.qll +++ b/javascript/ql/lib/semmle/javascript/frameworks/TrustedTypes.qll @@ -11,6 +11,7 @@ private import semmle.javascript.security.dataflow.CodeInjectionCustomizations * Module for working with uses of the [Trusted Types API](https://developer.mozilla.org/en-US/docs/Web/API/Trusted_Types_API). */ module TrustedTypes { + overlay[local?] private class TrustedTypesEntry extends API::EntryPoint { TrustedTypesEntry() { this = "TrustedTypesEntry" } diff --git a/javascript/ql/lib/semmle/javascript/frameworks/Vue.qll b/javascript/ql/lib/semmle/javascript/frameworks/Vue.qll index f571648294c2..1052e91d4c13 100644 --- a/javascript/ql/lib/semmle/javascript/frameworks/Vue.qll +++ b/javascript/ql/lib/semmle/javascript/frameworks/Vue.qll @@ -7,6 +7,7 @@ import semmle.javascript.ViewComponentInput module Vue { /** The global variable `Vue`, as an API graph entry point. */ + overlay[local?] private class GlobalVueEntryPoint extends API::EntryPoint { GlobalVueEntryPoint() { this = "VueEntryPoint" } @@ -18,6 +19,7 @@ module Vue { * * This `EntryPoint` is used by `SingleFileComponent::getOwnOptions()`. */ + overlay[local?] private class VueExportEntryPoint extends API::EntryPoint { VueExportEntryPoint() { this = "VueExportEntryPoint" } @@ -437,6 +439,7 @@ module Vue { * * This entry point is used in `SingleFileComponent::getComponentRef()`. */ + overlay[local?] private class VueFileImportEntryPoint extends API::EntryPoint { VueFileImportEntryPoint() { this = "VueFileImportEntryPoint" } diff --git a/javascript/ql/lib/semmle/javascript/frameworks/WebResponse.qll b/javascript/ql/lib/semmle/javascript/frameworks/WebResponse.qll index dfdee73c9d90..9c24f84ecbc8 100644 --- a/javascript/ql/lib/semmle/javascript/frameworks/WebResponse.qll +++ b/javascript/ql/lib/semmle/javascript/frameworks/WebResponse.qll @@ -5,6 +5,7 @@ private import javascript /** Treats `Response` as an entry point for API graphs. */ +overlay[local?] private class ResponseEntryPoint extends API::EntryPoint { ResponseEntryPoint() { this = "global.Response" } @@ -12,6 +13,7 @@ private class ResponseEntryPoint extends API::EntryPoint { } /** Treats `Headers` as an entry point for API graphs. */ +overlay[local?] private class HeadersEntryPoint extends API::EntryPoint { HeadersEntryPoint() { this = "global.Headers" } diff --git a/javascript/ql/lib/semmle/javascript/frameworks/WebSocket.qll b/javascript/ql/lib/semmle/javascript/frameworks/WebSocket.qll index f71b1cf9e0d6..2ec1b784f19f 100644 --- a/javascript/ql/lib/semmle/javascript/frameworks/WebSocket.qll +++ b/javascript/ql/lib/semmle/javascript/frameworks/WebSocket.qll @@ -48,6 +48,7 @@ private predicate areLibrariesCompatible( } /** Treats `WebSocket` as an entry point for API graphs. */ +overlay[local?] private class WebSocketEntryPoint extends API::EntryPoint { WebSocketEntryPoint() { this = "global.WebSocket" } @@ -55,6 +56,7 @@ private class WebSocketEntryPoint extends API::EntryPoint { } /** Treats `SockJS` as an entry point for API graphs. */ +overlay[local?] private class SockJSEntryPoint extends API::EntryPoint { SockJSEntryPoint() { this = "global.SockJS" } diff --git a/javascript/ql/lib/semmle/javascript/frameworks/Webix.qll b/javascript/ql/lib/semmle/javascript/frameworks/Webix.qll index effd49c632bf..3ce4e78ba3a3 100644 --- a/javascript/ql/lib/semmle/javascript/frameworks/Webix.qll +++ b/javascript/ql/lib/semmle/javascript/frameworks/Webix.qll @@ -9,6 +9,7 @@ private import javascript */ module Webix { /** The global variable `webix` as an entry point for API graphs. */ + overlay[local?] private class WebixGlobalEntry extends API::EntryPoint { WebixGlobalEntry() { this = "WebixGlobalEntry" } diff --git a/javascript/ql/lib/semmle/javascript/frameworks/data/internal/ApiGraphModels.qll b/javascript/ql/lib/semmle/javascript/frameworks/data/internal/ApiGraphModels.qll index 80ec45a3cf17..68f2210bff28 100644 --- a/javascript/ql/lib/semmle/javascript/frameworks/data/internal/ApiGraphModels.qll +++ b/javascript/ql/lib/semmle/javascript/frameworks/data/internal/ApiGraphModels.qll @@ -492,6 +492,7 @@ private predicate invocationMatchesCallSiteFilter( Specific::invocationMatchesExtraCallSiteFilter(invoke, token) } +overlay[local?] private class TypeModelUseEntry extends API::EntryPoint { private string type; @@ -505,6 +506,7 @@ private class TypeModelUseEntry extends API::EntryPoint { API::Node getNodeForType(string type_) { type = type_ and result = this.getANode() } } +overlay[local?] private class TypeModelDefEntry extends API::EntryPoint { private string type; diff --git a/javascript/ql/lib/semmle/javascript/frameworks/data/internal/ApiGraphModelsSpecific.qll b/javascript/ql/lib/semmle/javascript/frameworks/data/internal/ApiGraphModelsSpecific.qll index 2074b18600dc..7904a2b76c6c 100644 --- a/javascript/ql/lib/semmle/javascript/frameworks/data/internal/ApiGraphModelsSpecific.qll +++ b/javascript/ql/lib/semmle/javascript/frameworks/data/internal/ApiGraphModelsSpecific.qll @@ -93,6 +93,7 @@ private predicate parseRelevantTypeString(string rawType, string package, string } /** Holds if `global` is a global variable referenced via a the `global` package in a CSV row. */ +overlay[local] private predicate isRelevantGlobal(string global) { exists(AccessPath path, AccessPathToken token | isRelevantFullPath("global", path) and @@ -103,6 +104,7 @@ private predicate isRelevantGlobal(string global) { } /** An API graph entry point for global variables mentioned in a model. */ +overlay[local?] private class GlobalApiEntryPoint extends API::EntryPoint { string global; diff --git a/javascript/ql/lib/semmle/javascript/security/dataflow/RemoteFlowSources.qll b/javascript/ql/lib/semmle/javascript/security/dataflow/RemoteFlowSources.qll index 58600c579a84..9f4975e605ae 100644 --- a/javascript/ql/lib/semmle/javascript/security/dataflow/RemoteFlowSources.qll +++ b/javascript/ql/lib/semmle/javascript/security/dataflow/RemoteFlowSources.qll @@ -114,6 +114,7 @@ class ClientSideRemoteFlowKind extends string { * `name` and `address` of global variable `user` should be considered as remote flow sources with * source type "user input". */ +overlay[local?] private class RemoteFlowSourceAccessPath extends JsonString { string sourceType; @@ -167,6 +168,7 @@ private class RemoteFlowSourceAccessPath extends JsonString { * The global variable referenced by a `RemoteFlowSourceAccessPath`, declared as an API * entry point. */ +overlay[local?] private class ExternalRemoteFlowSourceSpecEntryPoint extends API::EntryPoint { string name; diff --git a/javascript/ql/lib/semmle/javascript/security/dataflow/SecondOrderCommandInjectionCustomizations.qll b/javascript/ql/lib/semmle/javascript/security/dataflow/SecondOrderCommandInjectionCustomizations.qll index 416ad56bef16..afac6f91d071 100644 --- a/javascript/ql/lib/semmle/javascript/security/dataflow/SecondOrderCommandInjectionCustomizations.qll +++ b/javascript/ql/lib/semmle/javascript/security/dataflow/SecondOrderCommandInjectionCustomizations.qll @@ -129,6 +129,7 @@ module SecondOrderCommandInjection { /** * A sink that invokes a command described by the `VulnerableCommand` class. */ + overlay[global] abstract class VulnerableCommandSink extends Sink { VulnerableCommand cmd; diff --git a/javascript/ql/lib/semmle/javascript/security/dataflow/TaintedPathCustomizations.qll b/javascript/ql/lib/semmle/javascript/security/dataflow/TaintedPathCustomizations.qll index f863b86a3b57..a09edf432f69 100644 --- a/javascript/ql/lib/semmle/javascript/security/dataflow/TaintedPathCustomizations.qll +++ b/javascript/ql/lib/semmle/javascript/security/dataflow/TaintedPathCustomizations.qll @@ -194,6 +194,7 @@ module TaintedPath { * There are currently four flow labels, representing the different combinations of * normalization and absoluteness. */ + overlay[global] abstract class PosixPath extends DataFlow::FlowLabel { Normalization normalization; Relativeness relativeness; diff --git a/javascript/ql/lib/semmle/javascript/security/dataflow/UnsafeHtmlConstructionCustomizations.qll b/javascript/ql/lib/semmle/javascript/security/dataflow/UnsafeHtmlConstructionCustomizations.qll index 06bad34b80c4..ab22c794fa56 100644 --- a/javascript/ql/lib/semmle/javascript/security/dataflow/UnsafeHtmlConstructionCustomizations.qll +++ b/javascript/ql/lib/semmle/javascript/security/dataflow/UnsafeHtmlConstructionCustomizations.qll @@ -101,6 +101,7 @@ module UnsafeHtmlConstruction { * A sink for `js/html-constructed-from-input` that constructs some HTML where * that HTML is later used in `xssSink`. */ + overlay[global] abstract class XssSink extends Sink { DomBasedXss::Sink xssSink; diff --git a/javascript/ql/test/ApiGraphs/custom-entry-point/VerifyAssertions.ql b/javascript/ql/test/ApiGraphs/custom-entry-point/VerifyAssertions.ql index 3502c0ea5561..89ab2f3f9449 100644 --- a/javascript/ql/test/ApiGraphs/custom-entry-point/VerifyAssertions.ql +++ b/javascript/ql/test/ApiGraphs/custom-entry-point/VerifyAssertions.ql @@ -1,3 +1,4 @@ +overlay[local?] class CustomEntryPoint extends API::EntryPoint { CustomEntryPoint() { this = "CustomEntryPoint" } diff --git a/javascript/ql/test/library-tests/EndpointNaming/EndpointNaming.expected b/javascript/ql/test/library-tests/EndpointNaming/EndpointNaming.expected index d2c34c887cc7..d56e036e0424 100644 --- a/javascript/ql/test/library-tests/EndpointNaming/EndpointNaming.expected +++ b/javascript/ql/test/library-tests/EndpointNaming/EndpointNaming.expected @@ -1,7 +1,7 @@ testFailures ambiguousPreferredPredecessor -| pack2/lib.js:1:1:3:1 | def moduleImport("pack2").getMember("exports").getMember("lib").getMember("LibClass").getInstance() | -| pack2/lib.js:8:22:8:34 | def moduleImport("pack2").getMember("exports").getMember("lib").getMember("LibClass").getMember("foo") | -| pack2/main.js:1:1:3:1 | def moduleImport("pack2").getMember("exports").getMember("MainClass").getInstance() | +| pack2/lib.js:1:1:3:1 | instance of class A ... ethod\\n} | +| pack2/lib.js:8:22:8:34 | def function() {} | +| pack2/main.js:1:1:3:1 | instance of class A ... ethod\\n} | ambiguousSinkName ambiguousFunctionName diff --git a/javascript/ql/test/library-tests/frameworks/Knex/test.expected b/javascript/ql/test/library-tests/frameworks/Knex/test.expected index 9ce25cd4fa58..bfa154226cd1 100644 --- a/javascript/ql/test/library-tests/frameworks/Knex/test.expected +++ b/javascript/ql/test/library-tests/frameworks/Knex/test.expected @@ -25,208 +25,208 @@ sqlString | tst.js:166:43:166:63 | 'col DE ... S LAST' | | tst.js:178:14:178:24 | 'count > ?' | knexLibrary -| file://:0:0:0:0 | use moduleImport("knex").getMember("exports") | +| file://:0:0:0:0 | module import knex | knexObject -| tst.js:3:14:3:30 | use moduleImport("knex").getMember("exports").getReturn() | -| tst.js:5:1:5:32 | use moduleImport("knex").getMember("exports").getReturn().getReturn() | -| tst.js:5:1:9:4 | use moduleImport("knex").getMember("exports").getReturn().getReturn().getMember("select").getReturn() | -| tst.js:5:1:10:52 | use moduleImport("knex").getMember("exports").getReturn().getReturn().getMember("select").getReturn().getMember("whereRaw").getReturn() | -| tst.js:12:1:12:48 | use moduleImport("knex").getMember("exports").getReturn().getMember("withUserParams").getReturn() | -| tst.js:12:1:12:59 | use moduleImport("knex").getMember("exports").getReturn().getMember("withUserParams").getReturn().getMember("table").getReturn() | -| tst.js:12:1:12:71 | use moduleImport("knex").getMember("exports").getReturn().getMember("withUserParams").getReturn().getMember("table").getReturn().getMember("select").getReturn() | -| tst.js:14:1:14:13 | use moduleImport("knex").getMember("exports").getReturn().getMember("select").getReturn() | -| tst.js:14:1:14:27 | use moduleImport("knex").getMember("exports").getReturn().getMember("select").getReturn().getMember("from").getReturn() | -| tst.js:14:1:14:41 | use moduleImport("knex").getMember("exports").getReturn().getMember("select").getReturn().getMember("from").getReturn().getMember("timeout").getReturn() | -| tst.js:15:1:15:38 | use moduleImport("knex").getMember("exports").getReturn().getMember("select").getReturn() | -| tst.js:15:1:15:52 | use moduleImport("knex").getMember("exports").getReturn().getMember("select").getReturn().getMember("from").getReturn() | -| tst.js:17:1:17:23 | use moduleImport("knex").getMember("exports").getReturn().getMember("avg").getReturn() | -| tst.js:17:1:19:4 | use moduleImport("knex").getMember("exports").getReturn().getMember("avg").getReturn().getMember("from").getReturn() | -| tst.js:17:1:19:24 | use moduleImport("knex").getMember("exports").getReturn().getMember("avg").getReturn().getMember("from").getReturn().getMember("as").getReturn() | -| tst.js:17:30:17:29 | use moduleImport("knex").getMember("exports").getReturn().getMember("avg").getReturn().getMember("from").getParameter(0).getReceiver() | -| tst.js:18:5:18:38 | use moduleImport("knex").getMember("exports").getReturn().getMember("avg").getReturn().getMember("from").getParameter(0).getReceiver().getMember("sum").getReturn() | -| tst.js:18:5:18:49 | use moduleImport("knex").getMember("exports").getReturn().getMember("avg").getReturn().getMember("from").getParameter(0).getReceiver().getMember("sum").getReturn().getMember("from").getReturn() | -| tst.js:18:5:18:68 | use moduleImport("knex").getMember("exports").getReturn().getMember("avg").getReturn().getMember("from").getParameter(0).getReceiver().getMember("sum").getReturn().getMember("from").getReturn().getMember("groupBy").getReturn() | -| tst.js:18:5:18:77 | use moduleImport("knex").getMember("exports").getReturn().getMember("avg").getReturn().getMember("from").getParameter(0).getReceiver().getMember("sum").getReturn().getMember("from").getReturn().getMember("groupBy").getReturn().getMember("as").getReturn() | -| tst.js:21:1:21:38 | use moduleImport("knex").getMember("exports").getReturn().getMember("column").getReturn() | -| tst.js:21:1:21:47 | use moduleImport("knex").getMember("exports").getReturn().getMember("column").getReturn().getMember("select").getReturn() | -| tst.js:21:1:21:61 | use moduleImport("knex").getMember("exports").getReturn().getMember("column").getReturn().getMember("select").getReturn().getMember("from").getReturn() | -| tst.js:23:1:23:16 | use moduleImport("knex").getMember("exports").getReturn().getMember("select").getReturn() | -| tst.js:23:1:23:30 | use moduleImport("knex").getMember("exports").getReturn().getMember("select").getReturn().getMember("from").getReturn() | -| tst.js:25:1:25:85 | use moduleImport("knex").getMember("exports").getReturn().getMember("with").getReturn() | -| tst.js:25:1:25:97 | use moduleImport("knex").getMember("exports").getReturn().getMember("with").getReturn().getMember("select").getReturn() | -| tst.js:25:1:25:116 | use moduleImport("knex").getMember("exports").getReturn().getMember("with").getReturn().getMember("select").getReturn().getMember("from").getReturn() | -| tst.js:25:25:25:84 | use moduleImport("knex").getMember("exports").getReturn().getMember("raw").getReturn() | -| tst.js:27:1:31:4 | use moduleImport("knex").getMember("exports").getReturn().getMember("withRecursive").getReturn() | -| tst.js:27:1:31:16 | use moduleImport("knex").getMember("exports").getReturn().getMember("withRecursive").getReturn().getMember("select").getReturn() | -| tst.js:27:1:31:34 | use moduleImport("knex").getMember("exports").getReturn().getMember("withRecursive").getReturn().getMember("select").getReturn().getMember("from").getReturn() | -| tst.js:33:1:33:25 | use moduleImport("knex").getMember("exports").getReturn().getMember("withSchema").getReturn() | -| tst.js:33:1:33:37 | use moduleImport("knex").getMember("exports").getReturn().getMember("withSchema").getReturn().getMember("select").getReturn() | -| tst.js:33:1:33:51 | use moduleImport("knex").getMember("exports").getReturn().getMember("withSchema").getReturn().getMember("select").getReturn().getMember("from").getReturn() | -| tst.js:35:1:35:13 | use moduleImport("knex").getMember("exports").getReturn().getReturn() | -| tst.js:35:1:38:4 | use moduleImport("knex").getMember("exports").getReturn().getReturn().getMember("where").getReturn() | -| tst.js:35:1:38:17 | use moduleImport("knex").getMember("exports").getReturn().getReturn().getMember("where").getReturn().getMember("select").getReturn() | -| tst.js:40:1:40:13 | use moduleImport("knex").getMember("exports").getReturn().getReturn() | -| tst.js:40:1:40:28 | use moduleImport("knex").getMember("exports").getReturn().getReturn().getMember("where").getReturn() | -| tst.js:42:1:42:13 | use moduleImport("knex").getMember("exports").getReturn().getReturn() | -| tst.js:42:1:45:3 | use moduleImport("knex").getMember("exports").getReturn().getReturn().getMember("where").getReturn() | -| tst.js:42:1:48:4 | use moduleImport("knex").getMember("exports").getReturn().getReturn().getMember("where").getReturn().getMember("andWhere").getReturn() | -| tst.js:46:13:46:12 | use moduleImport("knex").getMember("exports").getReturn().getReturn().getMember("where").getReturn().getMember("andWhere").getParameter(0).getReceiver() | -| tst.js:47:5:47:29 | use moduleImport("knex").getMember("exports").getReturn().getReturn().getMember("where").getReturn().getMember("andWhere").getParameter(0).getReceiver().getMember("where").getReturn() | -| tst.js:50:1:50:13 | use moduleImport("knex").getMember("exports").getReturn().getReturn() | -| tst.js:50:1:52:2 | use moduleImport("knex").getMember("exports").getReturn().getReturn().getMember("where").getReturn() | -| tst.js:50:1:52:28 | use moduleImport("knex").getMember("exports").getReturn().getReturn().getMember("where").getReturn().getMember("orWhere").getReturn() | -| tst.js:50:21:50:20 | use moduleImport("knex").getMember("exports").getReturn().getReturn().getMember("where").getParameter(0).getReceiver() | -| tst.js:51:3:51:21 | use moduleImport("knex").getMember("exports").getReturn().getReturn().getMember("where").getParameter(0).getReceiver().getMember("where").getReturn() | -| tst.js:51:3:51:44 | use moduleImport("knex").getMember("exports").getReturn().getReturn().getMember("where").getParameter(0).getReceiver().getMember("where").getReturn().getMember("orWhere").getReturn() | -| tst.js:54:1:54:13 | use moduleImport("knex").getMember("exports").getReturn().getReturn() | -| tst.js:54:1:54:56 | use moduleImport("knex").getMember("exports").getReturn().getReturn().getMember("where").getReturn() | -| tst.js:56:1:56:13 | use moduleImport("knex").getMember("exports").getReturn().getReturn() | -| tst.js:56:1:56:38 | use moduleImport("knex").getMember("exports").getReturn().getReturn().getMember("where").getReturn() | -| tst.js:58:18:58:30 | use moduleImport("knex").getMember("exports").getReturn().getReturn() | -| tst.js:58:18:58:55 | use moduleImport("knex").getMember("exports").getReturn().getReturn().getMember("where").getReturn() | -| tst.js:58:18:58:84 | use moduleImport("knex").getMember("exports").getReturn().getReturn().getMember("where").getReturn().getMember("andWhere").getReturn() | -| tst.js:58:18:58:108 | use moduleImport("knex").getMember("exports").getReturn().getReturn().getMember("where").getReturn().getMember("andWhere").getReturn().getMember("orWhere").getReturn() | -| tst.js:58:18:58:121 | use moduleImport("knex").getMember("exports").getReturn().getReturn().getMember("where").getReturn().getMember("andWhere").getReturn().getMember("orWhere").getReturn().getMember("select").getReturn() | -| tst.js:59:1:59:16 | use moduleImport("knex").getMember("exports").getReturn().getReturn() | -| tst.js:59:1:59:44 | use moduleImport("knex").getMember("exports").getReturn().getReturn().getMember("where").getReturn() | -| tst.js:61:1:61:13 | use moduleImport("knex").getMember("exports").getReturn().getReturn() | -| tst.js:61:1:61:28 | use moduleImport("knex").getMember("exports").getReturn().getReturn().getMember("where").getReturn() | -| tst.js:61:1:61:64 | use moduleImport("knex").getMember("exports").getReturn().getReturn().getMember("where").getReturn().getMember("orWhere").getReturn() | -| tst.js:63:1:63:13 | use moduleImport("knex").getMember("exports").getReturn().getReturn() | -| tst.js:63:1:66:2 | use moduleImport("knex").getMember("exports").getReturn().getReturn().getMember("whereNot").getReturn() | -| tst.js:63:1:66:15 | use moduleImport("knex").getMember("exports").getReturn().getReturn().getMember("whereNot").getReturn().getMember("select").getReturn() | -| tst.js:68:1:68:13 | use moduleImport("knex").getMember("exports").getReturn().getReturn() | -| tst.js:68:1:68:31 | use moduleImport("knex").getMember("exports").getReturn().getReturn().getMember("whereNot").getReturn() | -| tst.js:70:1:70:13 | use moduleImport("knex").getMember("exports").getReturn().getReturn() | -| tst.js:70:1:72:2 | use moduleImport("knex").getMember("exports").getReturn().getReturn().getMember("whereNot").getReturn() | -| tst.js:70:1:72:31 | use moduleImport("knex").getMember("exports").getReturn().getReturn().getMember("whereNot").getReturn().getMember("orWhereNot").getReturn() | -| tst.js:70:24:70:23 | use moduleImport("knex").getMember("exports").getReturn().getReturn().getMember("whereNot").getParameter(0).getReceiver() | -| tst.js:71:3:71:21 | use moduleImport("knex").getMember("exports").getReturn().getReturn().getMember("whereNot").getParameter(0).getReceiver().getMember("where").getReturn() | -| tst.js:71:3:71:47 | use moduleImport("knex").getMember("exports").getReturn().getReturn().getMember("whereNot").getParameter(0).getReceiver().getMember("where").getReturn().getMember("orWhereNot").getReturn() | -| tst.js:74:19:74:31 | use moduleImport("knex").getMember("exports").getReturn().getReturn() | -| tst.js:74:19:75:30 | use moduleImport("knex").getMember("exports").getReturn().getReturn().getMember("whereNot").getReturn() | -| tst.js:74:19:76:31 | use moduleImport("knex").getMember("exports").getReturn().getReturn().getMember("whereNot").getReturn().getMember("andWhere").getReturn() | -| tst.js:74:19:77:26 | use moduleImport("knex").getMember("exports").getReturn().getReturn().getMember("whereNot").getReturn().getMember("andWhere").getReturn().getMember("orWhere").getReturn() | -| tst.js:74:19:78:15 | use moduleImport("knex").getMember("exports").getReturn().getReturn().getMember("whereNot").getReturn().getMember("andWhere").getReturn().getMember("orWhere").getReturn().getMember("select").getReturn() | -| tst.js:80:1:80:16 | use moduleImport("knex").getMember("exports").getReturn().getReturn() | -| tst.js:80:1:80:49 | use moduleImport("knex").getMember("exports").getReturn().getReturn().getMember("where").getReturn() | -| tst.js:82:1:82:19 | use moduleImport("knex").getMember("exports").getReturn().getMember("select").getReturn() | -| tst.js:82:1:82:33 | use moduleImport("knex").getMember("exports").getReturn().getMember("select").getReturn().getMember("from").getReturn() | -| tst.js:82:1:83:27 | use moduleImport("knex").getMember("exports").getReturn().getMember("select").getReturn().getMember("from").getReturn().getMember("whereIn").getReturn() | -| tst.js:82:1:84:29 | use moduleImport("knex").getMember("exports").getReturn().getMember("select").getReturn().getMember("from").getReturn().getMember("whereIn").getReturn().getMember("orWhereIn").getReturn() | -| tst.js:86:1:86:19 | use moduleImport("knex").getMember("exports").getReturn().getMember("select").getReturn() | -| tst.js:86:1:86:33 | use moduleImport("knex").getMember("exports").getReturn().getMember("select").getReturn().getMember("from").getReturn() | -| tst.js:86:1:89:4 | use moduleImport("knex").getMember("exports").getReturn().getMember("select").getReturn().getMember("from").getReturn().getMember("whereIn").getReturn() | -| tst.js:91:1:91:13 | use moduleImport("knex").getMember("exports").getReturn().getReturn() | -| tst.js:91:1:91:41 | use moduleImport("knex").getMember("exports").getReturn().getReturn().getMember("whereNotIn").getReturn() | -| tst.js:93:1:93:13 | use moduleImport("knex").getMember("exports").getReturn().getReturn() | -| tst.js:93:1:93:45 | use moduleImport("knex").getMember("exports").getReturn().getReturn().getMember("where").getReturn() | -| tst.js:93:1:93:75 | use moduleImport("knex").getMember("exports").getReturn().getReturn().getMember("where").getReturn().getMember("orWhereNotIn").getReturn() | -| tst.js:95:1:95:13 | use moduleImport("knex").getMember("exports").getReturn().getReturn() | -| tst.js:95:1:95:37 | use moduleImport("knex").getMember("exports").getReturn().getReturn().getMember("whereNull").getReturn() | -| tst.js:97:1:97:13 | use moduleImport("knex").getMember("exports").getReturn().getReturn() | -| tst.js:97:1:97:40 | use moduleImport("knex").getMember("exports").getReturn().getReturn().getMember("whereNotNull").getReturn() | -| tst.js:99:1:99:13 | use moduleImport("knex").getMember("exports").getReturn().getReturn() | -| tst.js:99:1:101:2 | use moduleImport("knex").getMember("exports").getReturn().getReturn().getMember("whereExists").getReturn() | -| tst.js:99:27:99:26 | use moduleImport("knex").getMember("exports").getReturn().getReturn().getMember("whereExists").getParameter(0).getReceiver() | -| tst.js:100:3:100:18 | use moduleImport("knex").getMember("exports").getReturn().getReturn().getMember("whereExists").getParameter(0).getReceiver().getMember("select").getReturn() | -| tst.js:100:3:100:35 | use moduleImport("knex").getMember("exports").getReturn().getReturn().getMember("whereExists").getParameter(0).getReceiver().getMember("select").getReturn().getMember("from").getReturn() | -| tst.js:100:3:100:78 | use moduleImport("knex").getMember("exports").getReturn().getReturn().getMember("whereExists").getParameter(0).getReceiver().getMember("select").getReturn().getMember("from").getReturn().getMember("whereRaw").getReturn() | -| tst.js:103:1:103:13 | use moduleImport("knex").getMember("exports").getReturn().getReturn() | -| tst.js:103:1:103:103 | use moduleImport("knex").getMember("exports").getReturn().getReturn().getMember("whereExists").getReturn() | -| tst.js:103:27:103:42 | use moduleImport("knex").getMember("exports").getReturn().getMember("select").getReturn() | -| tst.js:103:27:103:59 | use moduleImport("knex").getMember("exports").getReturn().getMember("select").getReturn().getMember("from").getReturn() | -| tst.js:103:27:103:102 | use moduleImport("knex").getMember("exports").getReturn().getMember("select").getReturn().getMember("from").getReturn().getMember("whereRaw").getReturn() | -| tst.js:105:1:105:13 | use moduleImport("knex").getMember("exports").getReturn().getReturn() | -| tst.js:105:1:107:2 | use moduleImport("knex").getMember("exports").getReturn().getReturn().getMember("whereNotExists").getReturn() | -| tst.js:105:30:105:29 | use moduleImport("knex").getMember("exports").getReturn().getReturn().getMember("whereNotExists").getParameter(0).getReceiver() | -| tst.js:106:3:106:18 | use moduleImport("knex").getMember("exports").getReturn().getReturn().getMember("whereNotExists").getParameter(0).getReceiver().getMember("select").getReturn() | -| tst.js:106:3:106:35 | use moduleImport("knex").getMember("exports").getReturn().getReturn().getMember("whereNotExists").getParameter(0).getReceiver().getMember("select").getReturn().getMember("from").getReturn() | -| tst.js:106:3:106:78 | use moduleImport("knex").getMember("exports").getReturn().getReturn().getMember("whereNotExists").getParameter(0).getReceiver().getMember("select").getReturn().getMember("from").getReturn().getMember("whereRaw").getReturn() | -| tst.js:109:1:109:13 | use moduleImport("knex").getMember("exports").getReturn().getReturn() | -| tst.js:109:1:109:45 | use moduleImport("knex").getMember("exports").getReturn().getReturn().getMember("whereBetween").getReturn() | -| tst.js:111:1:111:13 | use moduleImport("knex").getMember("exports").getReturn().getReturn() | -| tst.js:111:1:111:48 | use moduleImport("knex").getMember("exports").getReturn().getReturn().getMember("whereNotBetween").getReturn() | -| tst.js:113:1:113:13 | use moduleImport("knex").getMember("exports").getReturn().getReturn() | -| tst.js:113:1:113:37 | use moduleImport("knex").getMember("exports").getReturn().getReturn().getMember("whereRaw").getReturn() | -| tst.js:115:1:115:13 | use moduleImport("knex").getMember("exports").getReturn().getReturn() | -| tst.js:115:1:116:56 | use moduleImport("knex").getMember("exports").getReturn().getReturn().getMember("join").getReturn() | -| tst.js:115:1:117:39 | use moduleImport("knex").getMember("exports").getReturn().getReturn().getMember("join").getReturn().getMember("select").getReturn() | -| tst.js:119:1:119:13 | use moduleImport("knex").getMember("exports").getReturn().getReturn() | -| tst.js:119:1:120:51 | use moduleImport("knex").getMember("exports").getReturn().getReturn().getMember("join").getReturn() | -| tst.js:119:1:121:39 | use moduleImport("knex").getMember("exports").getReturn().getReturn().getMember("join").getReturn().getMember("select").getReturn() | -| tst.js:123:1:123:16 | use moduleImport("knex").getMember("exports").getReturn().getMember("select").getReturn() | -| tst.js:123:1:123:30 | use moduleImport("knex").getMember("exports").getReturn().getMember("select").getReturn().getMember("from").getReturn() | -| tst.js:123:1:125:2 | use moduleImport("knex").getMember("exports").getReturn().getMember("select").getReturn().getMember("from").getReturn().getMember("join").getReturn() | -| tst.js:127:1:127:16 | use moduleImport("knex").getMember("exports").getReturn().getMember("select").getReturn() | -| tst.js:127:1:127:30 | use moduleImport("knex").getMember("exports").getReturn().getMember("select").getReturn().getMember("from").getReturn() | -| tst.js:127:1:132:2 | use moduleImport("knex").getMember("exports").getReturn().getMember("select").getReturn().getMember("from").getReturn().getMember("join").getReturn() | -| tst.js:134:1:134:16 | use moduleImport("knex").getMember("exports").getReturn().getMember("select").getReturn() | -| tst.js:134:1:134:30 | use moduleImport("knex").getMember("exports").getReturn().getMember("select").getReturn().getMember("from").getReturn() | -| tst.js:134:1:134:90 | use moduleImport("knex").getMember("exports").getReturn().getMember("select").getReturn().getMember("from").getReturn().getMember("join").getReturn() | -| tst.js:134:66:134:89 | use moduleImport("knex").getMember("exports").getReturn().getMember("raw").getReturn() | -| tst.js:136:1:136:18 | use moduleImport("knex").getMember("exports").getReturn().getMember("from").getReturn() | -| tst.js:136:1:136:72 | use moduleImport("knex").getMember("exports").getReturn().getMember("from").getReturn().getMember("innerJoin").getReturn() | -| tst.js:138:1:138:16 | use moduleImport("knex").getMember("exports").getReturn().getMember("select").getReturn() | -| tst.js:138:1:138:30 | use moduleImport("knex").getMember("exports").getReturn().getMember("select").getReturn().getMember("from").getReturn() | -| tst.js:138:1:138:83 | use moduleImport("knex").getMember("exports").getReturn().getMember("select").getReturn().getMember("from").getReturn().getMember("leftJoin").getReturn() | -| tst.js:140:1:140:16 | use moduleImport("knex").getMember("exports").getReturn().getMember("select").getReturn() | -| tst.js:140:1:140:30 | use moduleImport("knex").getMember("exports").getReturn().getMember("select").getReturn().getMember("from").getReturn() | -| tst.js:140:1:140:88 | use moduleImport("knex").getMember("exports").getReturn().getMember("select").getReturn().getMember("from").getReturn().getMember("leftOuterJoin").getReturn() | -| tst.js:142:1:142:16 | use moduleImport("knex").getMember("exports").getReturn().getMember("select").getReturn() | -| tst.js:142:1:142:30 | use moduleImport("knex").getMember("exports").getReturn().getMember("select").getReturn().getMember("from").getReturn() | -| tst.js:142:1:142:84 | use moduleImport("knex").getMember("exports").getReturn().getMember("select").getReturn().getMember("from").getReturn().getMember("rightJoin").getReturn() | -| tst.js:144:1:144:16 | use moduleImport("knex").getMember("exports").getReturn().getMember("select").getReturn() | -| tst.js:144:1:144:30 | use moduleImport("knex").getMember("exports").getReturn().getMember("select").getReturn().getMember("from").getReturn() | -| tst.js:144:1:144:89 | use moduleImport("knex").getMember("exports").getReturn().getMember("select").getReturn().getMember("from").getReturn().getMember("rightOuterJoin").getReturn() | -| tst.js:146:1:146:16 | use moduleImport("knex").getMember("exports").getReturn().getMember("select").getReturn() | -| tst.js:146:1:146:30 | use moduleImport("knex").getMember("exports").getReturn().getMember("select").getReturn().getMember("from").getReturn() | -| tst.js:146:1:146:88 | use moduleImport("knex").getMember("exports").getReturn().getMember("select").getReturn().getMember("from").getReturn().getMember("fullOuterJoin").getReturn() | -| tst.js:148:1:148:16 | use moduleImport("knex").getMember("exports").getReturn().getMember("select").getReturn() | -| tst.js:148:1:148:30 | use moduleImport("knex").getMember("exports").getReturn().getMember("select").getReturn().getMember("from").getReturn() | -| tst.js:148:1:148:52 | use moduleImport("knex").getMember("exports").getReturn().getMember("select").getReturn().getMember("from").getReturn().getMember("crossJoin").getReturn() | -| tst.js:150:1:150:16 | use moduleImport("knex").getMember("exports").getReturn().getMember("select").getReturn() | -| tst.js:150:1:150:33 | use moduleImport("knex").getMember("exports").getReturn().getMember("select").getReturn().getMember("from").getReturn() | -| tst.js:150:1:150:69 | use moduleImport("knex").getMember("exports").getReturn().getMember("select").getReturn().getMember("from").getReturn().getMember("joinRaw").getReturn() | -| tst.js:150:1:150:84 | use moduleImport("knex").getMember("exports").getReturn().getMember("select").getReturn().getMember("from").getReturn().getMember("joinRaw").getReturn().getMember("where").getReturn() | -| tst.js:152:1:152:16 | use moduleImport("knex").getMember("exports").getReturn().getMember("select").getReturn() | -| tst.js:152:1:152:30 | use moduleImport("knex").getMember("exports").getReturn().getMember("select").getReturn().getMember("from").getReturn() | -| tst.js:152:1:154:2 | use moduleImport("knex").getMember("exports").getReturn().getMember("select").getReturn().getMember("from").getReturn().getMember("join").getReturn() | -| tst.js:156:1:156:28 | use moduleImport("knex").getMember("exports").getReturn().getMember("select").getReturn() | -| tst.js:156:1:156:42 | use moduleImport("knex").getMember("exports").getReturn().getMember("select").getReturn().getMember("from").getReturn() | -| tst.js:156:1:156:63 | use moduleImport("knex").getMember("exports").getReturn().getMember("select").getReturn().getMember("from").getReturn().getMember("where").getReturn() | -| tst.js:156:1:156:79 | use moduleImport("knex").getMember("exports").getReturn().getMember("select").getReturn().getMember("from").getReturn().getMember("where").getReturn().getMember("clear").getReturn() | -| tst.js:156:1:156:94 | use moduleImport("knex").getMember("exports").getReturn().getMember("select").getReturn().getMember("from").getReturn().getMember("where").getReturn().getMember("clear").getReturn().getMember("clear").getReturn() | -| tst.js:158:1:158:17 | use moduleImport("knex").getMember("exports").getReturn().getReturn() | -| tst.js:158:1:158:53 | use moduleImport("knex").getMember("exports").getReturn().getReturn().getMember("distinct").getReturn() | -| tst.js:160:1:160:13 | use moduleImport("knex").getMember("exports").getReturn().getReturn() | -| tst.js:160:1:160:31 | use moduleImport("knex").getMember("exports").getReturn().getReturn().getMember("distinctOn").getReturn() | -| tst.js:162:1:162:44 | use moduleImport("knex").getMember("exports").getReturn().getMember("select").getReturn() | -| tst.js:162:1:162:58 | use moduleImport("knex").getMember("exports").getReturn().getMember("select").getReturn().getMember("from").getReturn() | -| tst.js:162:1:162:89 | use moduleImport("knex").getMember("exports").getReturn().getMember("select").getReturn().getMember("from").getReturn().getMember("groupByRaw").getReturn() | -| tst.js:162:21:162:43 | use moduleImport("knex").getMember("exports").getReturn().getMember("raw").getReturn() | -| tst.js:164:1:164:13 | use moduleImport("knex").getMember("exports").getReturn().getReturn() | -| tst.js:164:1:164:30 | use moduleImport("knex").getMember("exports").getReturn().getReturn().getMember("orderBy").getReturn() | -| tst.js:166:1:166:16 | use moduleImport("knex").getMember("exports").getReturn().getMember("select").getReturn() | -| tst.js:166:1:166:30 | use moduleImport("knex").getMember("exports").getReturn().getMember("select").getReturn().getMember("from").getReturn() | -| tst.js:166:1:166:64 | use moduleImport("knex").getMember("exports").getReturn().getMember("select").getReturn().getMember("from").getReturn().getMember("orderByRaw").getReturn() | -| tst.js:168:1:168:13 | use moduleImport("knex").getMember("exports").getReturn().getReturn() | -| tst.js:168:1:169:19 | use moduleImport("knex").getMember("exports").getReturn().getReturn().getMember("groupBy").getReturn() | -| tst.js:168:1:170:26 | use moduleImport("knex").getMember("exports").getReturn().getReturn().getMember("groupBy").getReturn().getMember("orderBy").getReturn() | -| tst.js:168:1:171:28 | use moduleImport("knex").getMember("exports").getReturn().getReturn().getMember("groupBy").getReturn().getMember("orderBy").getReturn().getMember("having").getReturn() | -| tst.js:173:1:173:16 | use moduleImport("knex").getMember("exports").getReturn().getMember("select").getReturn() | -| tst.js:173:1:173:30 | use moduleImport("knex").getMember("exports").getReturn().getMember("select").getReturn().getMember("from").getReturn() | -| tst.js:173:1:173:61 | use moduleImport("knex").getMember("exports").getReturn().getMember("select").getReturn().getMember("from").getReturn().getMember("havingIn").getReturn() | -| tst.js:175:1:175:13 | use moduleImport("knex").getMember("exports").getReturn().getReturn() | -| tst.js:175:1:176:19 | use moduleImport("knex").getMember("exports").getReturn().getReturn().getMember("groupBy").getReturn() | -| tst.js:175:1:177:26 | use moduleImport("knex").getMember("exports").getReturn().getReturn().getMember("groupBy").getReturn().getMember("orderBy").getReturn() | -| tst.js:175:1:178:32 | use moduleImport("knex").getMember("exports").getReturn().getReturn().getMember("groupBy").getReturn().getMember("orderBy").getReturn().getMember("havingRaw").getReturn() | -| tst.js:180:1:180:16 | use moduleImport("knex").getMember("exports").getReturn().getMember("select").getReturn() | -| tst.js:181:1:181:16 | use moduleImport("knex").getMember("exports").getReturn().getMember("select").getReturn() | -| tst.js:182:1:182:16 | use moduleImport("knex").getMember("exports").getReturn().getMember("select").getReturn() | -| tst.js:183:1:183:16 | use moduleImport("knex").getMember("exports").getReturn().getMember("select").getReturn() | -| tst.js:184:1:184:16 | use moduleImport("knex").getMember("exports").getReturn().getMember("select").getReturn() | -| tst.js:185:1:185:16 | use moduleImport("knex").getMember("exports").getReturn().getMember("select").getReturn() | -| tst.js:186:1:186:16 | use moduleImport("knex").getMember("exports").getReturn().getMember("select").getReturn() | -| tst.js:187:1:187:16 | use moduleImport("knex").getMember("exports").getReturn().getMember("select").getReturn() | -| tst.js:188:1:188:16 | use moduleImport("knex").getMember("exports").getReturn().getMember("select").getReturn() | +| tst.js:3:14:3:30 | use require('knex')() | +| tst.js:5:1:5:32 | use knex({ ... ble' }) | +| tst.js:5:1:9:4 | use knex({ ... e'\\n }) | +| tst.js:5:1:10:52 | use knex({ ... mn_2']) | +| tst.js:12:1:12:48 | use knex.wi ... ble1'}) | +| tst.js:12:1:12:59 | use knex.wi ... le('t') | +| tst.js:12:1:12:71 | use knex.wi ... ct('x') | +| tst.js:14:1:14:13 | use knex.select() | +| tst.js:14:1:14:27 | use knex.se ... books') | +| tst.js:14:1:14:41 | use knex.se ... t(1000) | +| tst.js:15:1:15:38 | use knex.se ... 'year') | +| tst.js:15:1:15:52 | use knex.se ... books') | +| tst.js:17:1:17:23 | use knex.av ... lumn1') | +| tst.js:17:1:19:4 | use knex.av ... ')\\n }) | +| tst.js:17:1:19:24 | use knex.av ... alias') | +| tst.js:17:30:17:29 | use this | +| tst.js:18:5:18:38 | use this.su ... lumn1') | +| tst.js:18:5:18:49 | use this.su ... m('t1') | +| tst.js:18:5:18:68 | use this.su ... lumn1') | +| tst.js:18:5:18:77 | use this.su ... s('t1') | +| tst.js:21:1:21:38 | use knex.co ... 'year') | +| tst.js:21:1:21:47 | use knex.co ... elect() | +| tst.js:21:1:21:61 | use knex.co ... books') | +| tst.js:23:1:23:16 | use knex.select('*') | +| tst.js:23:1:23:30 | use knex.se ... users') | +| tst.js:25:1:25:85 | use knex.wi ... Test')) | +| tst.js:25:1:25:97 | use knex.wi ... ct('*') | +| tst.js:25:1:25:116 | use knex.wi ... alias') | +| tst.js:25:25:25:84 | use knex.ra ... 'Test') | +| tst.js:27:1:31:4 | use knex.wi ... })\\n }) | +| tst.js:27:1:31:16 | use knex.wi ... ct('*') | +| tst.js:27:1:31:34 | use knex.wi ... stors') | +| tst.js:33:1:33:25 | use knex.wi ... ublic') | +| tst.js:33:1:33:37 | use knex.wi ... ct('*') | +| tst.js:33:1:33:51 | use knex.wi ... users') | +| tst.js:35:1:35:13 | use knex('users') | +| tst.js:35:1:38:4 | use knex('u ... r'\\n }) | +| tst.js:35:1:38:17 | use knex('u ... t('id') | +| tst.js:40:1:40:13 | use knex('users') | +| tst.js:40:1:40:28 | use knex('u ... id', 1) | +| tst.js:42:1:42:13 | use knex('users') | +| tst.js:42:1:45:3 | use knex('u ... 9])\\n ) | +| tst.js:42:1:48:4 | use knex('u ... 0)\\n }) | +| tst.js:46:13:46:12 | use this | +| tst.js:47:5:47:29 | use this.wh ... >', 10) | +| tst.js:50:1:50:13 | use knex('users') | +| tst.js:50:1:52:2 | use knex('u ... 10)\\n}) | +| tst.js:50:1:52:28 | use knex('u ... ster'}) | +| tst.js:50:21:50:20 | use this | +| tst.js:51:3:51:21 | use this.where('id', 1) | +| tst.js:51:3:51:44 | use this.wh ... >', 10) | +| tst.js:54:1:54:13 | use knex('users') | +| tst.js:54:1:54:56 | use knex('u ... keme%') | +| tst.js:56:1:56:13 | use knex('users') | +| tst.js:56:1:56:38 | use knex('u ... ', 100) | +| tst.js:58:18:58:30 | use knex('users') | +| tst.js:58:18:58:55 | use knex('u ... ', 100) | +| tst.js:58:18:58:84 | use knex('u ... ctive') | +| tst.js:58:18:58:108 | use knex('u ... 'John') | +| tst.js:58:18:58:121 | use knex('u ... t('id') | +| tst.js:59:1:59:16 | use knex('accounts') | +| tst.js:59:1:59:44 | use knex('a ... bquery) | +| tst.js:61:1:61:13 | use knex('users') | +| tst.js:61:1:61:28 | use knex('u ... id', 1) | +| tst.js:61:1:61:64 | use knex('u ... knex'}) | +| tst.js:63:1:63:13 | use knex('users') | +| tst.js:63:1:66:2 | use knex('u ... ser'\\n}) | +| tst.js:63:1:66:15 | use knex('u ... t('id') | +| tst.js:68:1:68:13 | use knex('users') | +| tst.js:68:1:68:31 | use knex('u ... id', 1) | +| tst.js:70:1:70:13 | use knex('users') | +| tst.js:70:1:72:2 | use knex('u ... 10)\\n}) | +| tst.js:70:1:72:31 | use knex('u ... ster'}) | +| tst.js:70:24:70:23 | use this | +| tst.js:71:3:71:21 | use this.where('id', 1) | +| tst.js:71:3:71:47 | use this.wh ... >', 10) | +| tst.js:74:19:74:31 | use knex('users') | +| tst.js:74:19:75:30 | use knex('u ... ', 100) | +| tst.js:74:19:76:31 | use knex('u ... ctive') | +| tst.js:74:19:77:26 | use knex('u ... 'John') | +| tst.js:74:19:78:15 | use knex('u ... t('id') | +| tst.js:80:1:80:16 | use knex('accounts') | +| tst.js:80:1:80:49 | use knex('a ... query2) | +| tst.js:82:1:82:19 | use knex.select('name') | +| tst.js:82:1:82:33 | use knex.se ... users') | +| tst.js:82:1:83:27 | use knex.se ... 2, 3]) | +| tst.js:82:1:84:29 | use knex.se ... 5, 6]) | +| tst.js:86:1:86:19 | use knex.select('name') | +| tst.js:86:1:86:33 | use knex.se ... users') | +| tst.js:86:1:89:4 | use knex.se ... );\\n }) | +| tst.js:91:1:91:13 | use knex('users') | +| tst.js:91:1:91:41 | use knex('u ... 2, 3]) | +| tst.js:93:1:93:13 | use knex('users') | +| tst.js:93:1:93:45 | use knex('u ... Test%') | +| tst.js:93:1:93:75 | use knex('u ... 2, 3]) | +| tst.js:95:1:95:13 | use knex('users') | +| tst.js:95:1:95:37 | use knex('u ... ed_at') | +| tst.js:97:1:97:13 | use knex('users') | +| tst.js:97:1:97:40 | use knex('u ... ed_at') | +| tst.js:99:1:99:13 | use knex('users') | +| tst.js:99:1:101:2 | use knex('u ... d');\\n}) | +| tst.js:99:27:99:26 | use this | +| tst.js:100:3:100:18 | use this.select('*') | +| tst.js:100:3:100:35 | use this.se ... ounts') | +| tst.js:100:3:100:78 | use this.se ... ts.id') | +| tst.js:103:1:103:13 | use knex('users') | +| tst.js:103:1:103:103 | use knex('u ... s.id')) | +| tst.js:103:27:103:42 | use knex.select('*') | +| tst.js:103:27:103:59 | use knex.se ... ounts') | +| tst.js:103:27:103:102 | use knex.se ... ts.id') | +| tst.js:105:1:105:13 | use knex('users') | +| tst.js:105:1:107:2 | use knex('u ... d');\\n}) | +| tst.js:105:30:105:29 | use this | +| tst.js:106:3:106:18 | use this.select('*') | +| tst.js:106:3:106:35 | use this.se ... ounts') | +| tst.js:106:3:106:78 | use this.se ... ts.id') | +| tst.js:109:1:109:13 | use knex('users') | +| tst.js:109:1:109:45 | use knex('u ... , 100]) | +| tst.js:111:1:111:13 | use knex('users') | +| tst.js:111:1:111:48 | use knex('u ... , 100]) | +| tst.js:113:1:113:13 | use knex('users') | +| tst.js:113:1:113:37 | use knex('u ... ', [1]) | +| tst.js:115:1:115:13 | use knex('users') | +| tst.js:115:1:116:56 | use knex('u ... er_id') | +| tst.js:115:1:117:39 | use knex('u ... phone') | +| tst.js:119:1:119:13 | use knex('users') | +| tst.js:119:1:120:51 | use knex('u ... er_id') | +| tst.js:119:1:121:39 | use knex('u ... phone') | +| tst.js:123:1:123:16 | use knex.select('*') | +| tst.js:123:1:123:30 | use knex.se ... users') | +| tst.js:123:1:125:2 | use knex.se ... id')\\n}) | +| tst.js:127:1:127:16 | use knex.select('*') | +| tst.js:127:1:127:30 | use knex.se ... users') | +| tst.js:127:1:132:2 | use knex.se ... })\\n}) | +| tst.js:134:1:134:16 | use knex.select('*') | +| tst.js:134:1:134:30 | use knex.se ... users') | +| tst.js:134:1:134:90 | use knex.se ... min'])) | +| tst.js:134:66:134:89 | use knex.ra ... dmin']) | +| tst.js:136:1:136:18 | use knex.from('users') | +| tst.js:136:1:136:72 | use knex.fr ... er_id') | +| tst.js:138:1:138:16 | use knex.select('*') | +| tst.js:138:1:138:30 | use knex.se ... users') | +| tst.js:138:1:138:83 | use knex.se ... er_id') | +| tst.js:140:1:140:16 | use knex.select('*') | +| tst.js:140:1:140:30 | use knex.se ... users') | +| tst.js:140:1:140:88 | use knex.se ... er_id') | +| tst.js:142:1:142:16 | use knex.select('*') | +| tst.js:142:1:142:30 | use knex.se ... users') | +| tst.js:142:1:142:84 | use knex.se ... er_id') | +| tst.js:144:1:144:16 | use knex.select('*') | +| tst.js:144:1:144:30 | use knex.se ... users') | +| tst.js:144:1:144:89 | use knex.se ... er_id') | +| tst.js:146:1:146:16 | use knex.select('*') | +| tst.js:146:1:146:30 | use knex.se ... users') | +| tst.js:146:1:146:88 | use knex.se ... er_id') | +| tst.js:148:1:148:16 | use knex.select('*') | +| tst.js:148:1:148:30 | use knex.se ... users') | +| tst.js:148:1:148:52 | use knex.se ... ounts') | +| tst.js:150:1:150:16 | use knex.select('*') | +| tst.js:150:1:150:33 | use knex.se ... ounts') | +| tst.js:150:1:150:69 | use knex.se ... able1') | +| tst.js:150:1:150:84 | use knex.se ... id', 1) | +| tst.js:152:1:152:16 | use knex.select('*') | +| tst.js:152:1:152:30 | use knex.se ... users') | +| tst.js:152:1:154:2 | use knex.se ... il')\\n}) | +| tst.js:156:1:156:28 | use knex.se ... 'name') | +| tst.js:156:1:156:42 | use knex.se ... users') | +| tst.js:156:1:156:63 | use knex.se ... <', 10) | +| tst.js:156:1:156:79 | use knex.se ... elect') | +| tst.js:156:1:156:94 | use knex.se ... where') | +| tst.js:158:1:158:17 | use knex('customers') | +| tst.js:158:1:158:53 | use knex('c ... _name') | +| tst.js:160:1:160:13 | use knex('users') | +| tst.js:160:1:160:31 | use knex('u ... ('age') | +| tst.js:162:1:162:44 | use knex.se ... fit)')) | +| tst.js:162:1:162:58 | use knex.se ... sales') | +| tst.js:162:1:162:89 | use knex.se ... OLLUP') | +| tst.js:162:21:162:43 | use knex.ra ... ofit)') | +| tst.js:164:1:164:13 | use knex('users') | +| tst.js:164:1:164:30 | use knex('u ... email') | +| tst.js:166:1:166:16 | use knex.select('*') | +| tst.js:166:1:166:30 | use knex.se ... table') | +| tst.js:166:1:166:64 | use knex.se ... LAST') | +| tst.js:168:1:168:13 | use knex('users') | +| tst.js:168:1:169:19 | use knex('u ... count') | +| tst.js:168:1:170:26 | use knex('u ... 'desc') | +| tst.js:168:1:171:28 | use knex('u ... ', 100) | +| tst.js:173:1:173:16 | use knex.select('*') | +| tst.js:173:1:173:30 | use knex.se ... users') | +| tst.js:173:1:173:61 | use knex.se ... 0, 17]) | +| tst.js:175:1:175:13 | use knex('users') | +| tst.js:175:1:176:19 | use knex('u ... count') | +| tst.js:175:1:177:26 | use knex('u ... 'desc') | +| tst.js:175:1:178:32 | use knex('u ... [100]) | +| tst.js:180:1:180:16 | use knex.select('x') | +| tst.js:181:1:181:16 | use knex.select('x') | +| tst.js:182:1:182:16 | use knex.select('x') | +| tst.js:183:1:183:16 | use knex.select('x') | +| tst.js:184:1:184:16 | use knex.select('x') | +| tst.js:185:1:185:16 | use knex.select('x') | +| tst.js:186:1:186:16 | use knex.select('x') | +| tst.js:187:1:187:16 | use knex.select('x') | +| tst.js:188:1:188:16 | use knex.select('x') | diff --git a/javascript/ql/test/library-tests/frameworks/Redux/test.expected b/javascript/ql/test/library-tests/frameworks/Redux/test.expected index 62997826b366..cbf4526ab08f 100644 --- a/javascript/ql/test/library-tests/frameworks/Redux/test.expected +++ b/javascript/ql/test/library-tests/frameworks/Redux/test.expected @@ -126,11 +126,11 @@ getAffectedStateAccessPath | react-redux.jsx:61:13:61:25 | manualReducer | manual | | trivial.js:130:14:130:46 | wrapper ... state) | wrapped | getADispatchFunctionNode -| react-redux.jsx:65:20:65:32 | use moduleImport("react-redux").getMember("exports").getMember("useDispatch").getReturn() | +| react-redux.jsx:65:20:65:32 | use useDispatch() | getADispatchedValueNode -| react-redux.jsx:27:12:30:5 | def entryPoint("react-redux-connect").getParameter(1).getMember("manualAction").getReturn() | -| react-redux.jsx:69:18:69:39 | def moduleImport("react-redux").getMember("exports").getMember("useDispatch").getReturn().getParameter(0) | -| react-redux.jsx:70:18:70:38 | def moduleImport("react-redux").getMember("exports").getMember("useDispatch").getReturn().getParameter(0) | +| react-redux.jsx:27:12:30:5 | def {\\n ... x\\n } | +| react-redux.jsx:69:18:69:39 | def manualA ... urce()) | +| react-redux.jsx:70:18:70:38 | def asyncAc ... urce()) | getAnUntypedActionInReducer | exportedReducer.js:12:20:12:25 | action | | react-redux.jsx:32:31:32:36 | action | @@ -167,23 +167,23 @@ reducerToStateStep | react-redux.jsx:39:42:39:55 | action.payload | react-redux.jsx:87:32:87:56 | state.m ... lValue2 | | react-redux.jsx:44:27:46:14 | [1, 2, ... }) | react-redux.jsx:88:32:88:56 | state.m ... lValue3 | getRootStateAccessPath -| manual | react-redux.jsx:86:31:86:42 | use entryPoint("react-redux-connect").getParameter(0).getParameter(0).getMember("manual") | -| manual | react-redux.jsx:87:32:87:43 | use entryPoint("react-redux-connect").getParameter(0).getParameter(0).getMember("manual") | -| manual | react-redux.jsx:88:32:88:43 | use entryPoint("react-redux-connect").getParameter(0).getParameter(0).getMember("manual") | -| manual.manualValue | react-redux.jsx:86:31:86:54 | use entryPoint("react-redux-connect").getParameter(0).getParameter(0).getMember("manual").getMember("manualValue") | -| manual.manualValue2 | react-redux.jsx:87:32:87:56 | use entryPoint("react-redux-connect").getParameter(0).getParameter(0).getMember("manual").getMember("manualValue2") | -| manual.manualValue3 | react-redux.jsx:88:32:88:56 | use entryPoint("react-redux-connect").getParameter(0).getParameter(0).getMember("manual").getMember("manualValue3") | -| toolkit | react-redux.jsx:84:32:84:44 | use entryPoint("react-redux-connect").getParameter(0).getParameter(0).getMember("toolkit") | -| toolkit | react-redux.jsx:85:24:85:36 | use entryPoint("react-redux-connect").getParameter(0).getParameter(0).getMember("toolkit") | -| toolkit.asyncValue | react-redux.jsx:85:24:85:47 | use entryPoint("react-redux-connect").getParameter(0).getParameter(0).getMember("toolkit").getMember("asyncValue") | -| toolkit.value | react-redux.jsx:84:32:84:50 | use entryPoint("react-redux-connect").getParameter(0).getParameter(0).getMember("toolkit").getMember("value") | -| x1 | accessPaths.js:8:16:8:52 | use moduleImport("react-redux").getMember("exports").getMember("useSelector").getForwardingFunction().getReturn() | -| x1 | accessPaths.js:8:44:8:51 | use moduleImport("react-redux").getMember("exports").getMember("useSelector").getParameter(0).getParameter(0).getMember("x1") | -| x2 | accessPaths.js:9:16:9:52 | use moduleImport("react-redux").getMember("exports").getMember("useSelector").getForwardingFunction().getReturn() | -| x2 | accessPaths.js:9:44:9:51 | use moduleImport("react-redux").getMember("exports").getMember("useSelector").getParameter(0).getParameter(0).getMember("x2") | -| x3 | accessPaths.js:10:16:10:52 | use moduleImport("react-redux").getMember("exports").getMember("useSelector").getForwardingFunction().getReturn() | -| x3 | accessPaths.js:10:44:10:51 | use moduleImport("react-redux").getMember("exports").getMember("useSelector").getParameter(0).getParameter(0).getMember("x3") | -| x4 | accessPaths.js:11:16:11:52 | use moduleImport("react-redux").getMember("exports").getMember("useSelector").getForwardingFunction().getReturn() | -| x4 | accessPaths.js:11:44:11:51 | use moduleImport("react-redux").getMember("exports").getMember("useSelector").getParameter(0).getParameter(0).getMember("x4") | -| x5 | accessPaths.js:12:16:12:52 | use moduleImport("react-redux").getMember("exports").getMember("useSelector").getForwardingFunction().getReturn() | -| x5 | accessPaths.js:12:44:12:51 | use moduleImport("react-redux").getMember("exports").getMember("useSelector").getParameter(0).getParameter(0).getMember("x5") | +| manual | react-redux.jsx:86:31:86:42 | use state.manual | +| manual | react-redux.jsx:87:32:87:43 | use state.manual | +| manual | react-redux.jsx:88:32:88:43 | use state.manual | +| manual.manualValue | react-redux.jsx:86:31:86:54 | use state.m ... alValue | +| manual.manualValue2 | react-redux.jsx:87:32:87:56 | use state.m ... lValue2 | +| manual.manualValue3 | react-redux.jsx:88:32:88:56 | use state.m ... lValue3 | +| toolkit | react-redux.jsx:84:32:84:44 | use state.toolkit | +| toolkit | react-redux.jsx:85:24:85:36 | use state.toolkit | +| toolkit.asyncValue | react-redux.jsx:85:24:85:47 | use state.t ... ncValue | +| toolkit.value | react-redux.jsx:84:32:84:50 | use state.toolkit.value | +| x1 | accessPaths.js:8:16:8:52 | use useSele ... ate.x1) | +| x1 | accessPaths.js:8:44:8:51 | use state.x1 | +| x2 | accessPaths.js:9:16:9:52 | use useSele ... ate.x2) | +| x2 | accessPaths.js:9:44:9:51 | use state.x2 | +| x3 | accessPaths.js:10:16:10:52 | use useSele ... ate.x3) | +| x3 | accessPaths.js:10:44:10:51 | use state.x3 | +| x4 | accessPaths.js:11:16:11:52 | use useSele ... ate.x4) | +| x4 | accessPaths.js:11:44:11:51 | use state.x4 | +| x5 | accessPaths.js:12:16:12:52 | use useSele ... ate.x5) | +| x5 | accessPaths.js:12:44:12:51 | use state.x5 | diff --git a/python/ql/lib/semmle/python/frameworks/data/internal/ApiGraphModels.qll b/python/ql/lib/semmle/python/frameworks/data/internal/ApiGraphModels.qll index 80ec45a3cf17..68f2210bff28 100644 --- a/python/ql/lib/semmle/python/frameworks/data/internal/ApiGraphModels.qll +++ b/python/ql/lib/semmle/python/frameworks/data/internal/ApiGraphModels.qll @@ -492,6 +492,7 @@ private predicate invocationMatchesCallSiteFilter( Specific::invocationMatchesExtraCallSiteFilter(invoke, token) } +overlay[local?] private class TypeModelUseEntry extends API::EntryPoint { private string type; @@ -505,6 +506,7 @@ private class TypeModelUseEntry extends API::EntryPoint { API::Node getNodeForType(string type_) { type = type_ and result = this.getANode() } } +overlay[local?] private class TypeModelDefEntry extends API::EntryPoint { private string type; diff --git a/ruby/ql/lib/codeql/ruby/frameworks/data/internal/ApiGraphModels.qll b/ruby/ql/lib/codeql/ruby/frameworks/data/internal/ApiGraphModels.qll index 80ec45a3cf17..68f2210bff28 100644 --- a/ruby/ql/lib/codeql/ruby/frameworks/data/internal/ApiGraphModels.qll +++ b/ruby/ql/lib/codeql/ruby/frameworks/data/internal/ApiGraphModels.qll @@ -492,6 +492,7 @@ private predicate invocationMatchesCallSiteFilter( Specific::invocationMatchesExtraCallSiteFilter(invoke, token) } +overlay[local?] private class TypeModelUseEntry extends API::EntryPoint { private string type; @@ -505,6 +506,7 @@ private class TypeModelUseEntry extends API::EntryPoint { API::Node getNodeForType(string type_) { type = type_ and result = this.getANode() } } +overlay[local?] private class TypeModelDefEntry extends API::EntryPoint { private string type;