From 8ea5fc76efb57382ae5073e51d08dd66838b98c3 Mon Sep 17 00:00:00 2001 From: Asger F Date: Fri, 10 Oct 2025 10:42:01 +0200 Subject: [PATCH 01/41] JS: Stop dependeding on getPath() for toString() --- javascript/ql/lib/semmle/javascript/ApiGraphs.qll | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/javascript/ql/lib/semmle/javascript/ApiGraphs.qll b/javascript/ql/lib/semmle/javascript/ApiGraphs.qll index 1a96e25b3b9f..85fda109ffaf 100644 --- a/javascript/ql/lib/semmle/javascript/ApiGraphs.qll +++ b/javascript/ql/lib/semmle/javascript/ApiGraphs.qll @@ -613,12 +613,12 @@ module API { /** A node corresponding to a definition of an API component. */ class Definition extends Node, Impl::TDef { - override string toString() { result = "def " + this.getPath() } + override string toString() { result = "def " + this.getInducingNode().toString() } } /** A node corresponding to the use of an API component. */ class Use extends Node, Impl::TUse { - override string toString() { result = "use " + this.getPath() } + override string toString() { result = "use " + this.getInducingNode().toString() } } /** Gets the root node. */ From 8212bf465e631e8caef5dc0ed13ffbc0f91a84b8 Mon Sep 17 00:00:00 2001 From: Asger F Date: Thu, 9 Oct 2025 13:01:14 +0200 Subject: [PATCH 02/41] JS: Make MkSyntheticCallbackArg() independent of trackUseNode --- javascript/ql/lib/semmle/javascript/ApiGraphs.qll | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/javascript/ql/lib/semmle/javascript/ApiGraphs.qll b/javascript/ql/lib/semmle/javascript/ApiGraphs.qll index 85fda109ffaf..3f9562075918 100644 --- a/javascript/ql/lib/semmle/javascript/ApiGraphs.qll +++ b/javascript/ql/lib/semmle/javascript/ApiGraphs.qll @@ -545,7 +545,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) } /** @@ -760,9 +760,7 @@ module API { MkTypeUse(string moduleName, string exportName) { any(TypeAnnotation n).hasUnderlyingType(moduleName, exportName) } or - MkSyntheticCallbackArg(DataFlow::Node src, int bound, DataFlow::InvokeNode nd) { - trackUseNode(src, true, bound, "").flowsTo(nd.getCalleeNode()) - } + MkSyntheticCallbackArg(DataFlow::InvokeNode nd) private predicate needsDefNode(DataFlow::ClassNode cls) { hasSemantics(cls) and @@ -1110,7 +1108,7 @@ module API { ) or exists(DataFlow::InvokeNode call | - base = MkSyntheticCallbackArg(_, _, call) and + base = MkSyntheticCallbackArg(call) and lbl = Label::parameter(1) and ref = awaited(call) ) @@ -1399,7 +1397,7 @@ module API { private DataFlow::SourceNode awaited(DataFlow::InvokeNode call, DataFlow::TypeTracker t) { t.startInPromise() and - exists(MkSyntheticCallbackArg(_, _, call)) and + trackUseNode(_, true, _, "").flowsTo(call.getCalleeNode()) and result = call or exists(DataFlow::TypeTracker t2 | result = awaited(call, t2).track(t2, t)) @@ -1494,7 +1492,8 @@ module API { DataFlow::InvokeNode getAPromisifiedInvocation(TApiNode callee, int bound, TApiNode succ) { exists(DataFlow::SourceNode src | Impl::use(callee, src) and - succ = Impl::MkSyntheticCallbackArg(src, bound, result) + trackUseNode(src, true, bound, "").flowsTo(result.getCalleeNode()) and + succ = Impl::MkSyntheticCallbackArg(result) ) } } From 4ca39e4aa1302037b86c6f175d906ac768ebfe60 Mon Sep 17 00:00:00 2001 From: Asger F Date: Thu, 9 Oct 2025 13:28:55 +0200 Subject: [PATCH 03/41] JS: Make other node types not depend on tracking predicates --- .../ql/lib/semmle/javascript/ApiGraphs.qll | 30 +++++++++++++++++-- 1 file changed, 27 insertions(+), 3 deletions(-) diff --git a/javascript/ql/lib/semmle/javascript/ApiGraphs.qll b/javascript/ql/lib/semmle/javascript/ApiGraphs.qll index 3f9562075918..6b3cb4b832d8 100644 --- a/javascript/ql/lib/semmle/javascript/ApiGraphs.qll +++ b/javascript/ql/lib/semmle/javascript/ApiGraphs.qll @@ -753,9 +753,31 @@ module API { 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 + MkClassInstance(DataFlow::ClassNode cls) or + MkDef(DataFlow::Node nd) { + nd = any(DataFlow::PropWrite w).getRhs() + or + nd = any(DataFlow::FunctionNode fn).getReturnNode() + or + 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) { any(TypeAnnotation n).hasUnderlyingType(moduleName, exportName) @@ -984,6 +1006,7 @@ module API { predicate rhs(TApiNode nd, DataFlow::Node rhs) { exists(string m | nd = MkModuleExport(m) | exports(m, rhs)) or + rhs(_, _, rhs) and nd = MkDef(rhs) } @@ -1246,6 +1269,7 @@ module API { ref = cls.(DataFlow::ClassNode).getAPrototypeReference() ) or + use(_, _, ref) and nd = MkUse(ref) } From ce07c8a26a32f006a409cce6e2e411e1316700fc Mon Sep 17 00:00:00 2001 From: Asger F Date: Thu, 9 Oct 2025 13:46:53 +0200 Subject: [PATCH 04/41] JS: Make use() and rhs() uncached and private --- javascript/ql/lib/semmle/javascript/ApiGraphs.qll | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/javascript/ql/lib/semmle/javascript/ApiGraphs.qll b/javascript/ql/lib/semmle/javascript/ApiGraphs.qll index 6b3cb4b832d8..5a4bdd6243dc 100644 --- a/javascript/ql/lib/semmle/javascript/ApiGraphs.qll +++ b/javascript/ql/lib/semmle/javascript/ApiGraphs.qll @@ -815,8 +815,7 @@ module API { * 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) { + private predicate rhs(TApiNode base, Label::ApiLabel lbl, DataFlow::Node rhs) { hasSemantics(rhs) and ( base = MkRoot() and From 9e1ab62b3aa0b96f4483cfe2e145c9fd9d8960af Mon Sep 17 00:00:00 2001 From: Asger F Date: Thu, 9 Oct 2025 13:55:38 +0200 Subject: [PATCH 05/41] JS: Wrap in a module Simply wraps everything in 'cached private module Stage {}' and adds 'import Stage'. The diff is large because of indentation changes. --- .../ql/lib/semmle/javascript/ApiGraphs.qll | 1219 ++++++++--------- 1 file changed, 604 insertions(+), 615 deletions(-) diff --git a/javascript/ql/lib/semmle/javascript/ApiGraphs.qll b/javascript/ql/lib/semmle/javascript/ApiGraphs.qll index 5a4bdd6243dc..9afc418c022b 100644 --- a/javascript/ql/lib/semmle/javascript/ApiGraphs.qll +++ b/javascript/ql/lib/semmle/javascript/ApiGraphs.qll @@ -811,693 +811,695 @@ module API { 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. - */ - private 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() + cached + 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. + */ + private 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 InvokeExpr getAnInvocationWithSpread(DataFlow::SourceNode node, int i) { - result = node.getAnInvocation().asExpr() and - i = firstSpreadIndex(result) - } + pragma[nomagic] + private int firstSpreadIndex(InvokeExpr expr) { + result = min(int i | expr.getArgument(i) instanceof SpreadElement) + } - 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 - ) - } + pragma[nomagic] + private InvokeExpr getAnInvocationWithSpread(DataFlow::SourceNode node, int i) { + result = node.getAnInvocation().asExpr() and + i = firstSpreadIndex(result) + } - /** - * 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 - rhs(_, _, rhs) and - nd = MkDef(rhs) - } + 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 `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 `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 - 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 = "") - } - - 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) - } + rhs(_, _, rhs) and + nd = MkDef(rhs) + } - /** - * 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 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 - // 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) - ) + SharedTypeTrackingStep::loadStep(pred.getALocalUse(), ref, Promises::valueProp()) and + lbl = Label::promised() and + (propDesc = Promises::valueProp() or propDesc = "") 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() + 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 + } + + 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. + */ + private 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() + ) or - lbl = Label::forwardingFunction() and - DataFlow::functionForwardingStep(pred.getALocalUse(), ref) + // 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::ClassNode cls | + exists(DataFlow::SourceNode src, DataFlow::SourceNode pred | + use(base, src) and pred = trackUseNode(src) + | lbl = Label::instance() and - cls = getALocalSubclassFwd(pred).getADirectSubClass*() + 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) | - ref = cls.getAReceiverNode() + exists(int i | + lbl = Label::parameter(i) and + ref = fn.getParameter(i) + ) or - ref = cls.getAClassReference().getAnInstantiation() + lbl = Label::receiver() and + ref = fn.getReceiver() ) 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(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::ContentSet contents | - SummaryTypeTracker::basicLoadStep(pred.getALocalUse(), ref, contents) and - lbl = Label::content(contents.getAStoreContent()) + exists(DataFlow::InvokeNode call | + base = MkSyntheticCallbackArg(call) and + lbl = Label::parameter(1) and + ref = awaited(call) ) + 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 + 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::Node def, DataFlow::FunctionNode fn | - rhs(base, def) and fn = trackDefNode(def) + 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 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 + or + fieldLike instanceof AccessorMethodDefinition | - exists(int i | - lbl = Label::parameter(i) and - ref = fn.getParameter(i) + 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()) ) + ) + } + + /** + * 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 - lbl = Label::receiver() and - ref = fn.getReceiver() + exists(DataFlow::Node base | use(MkModuleDef(m), base) | + ref = trackUseNode(base).getAPropertyRead("exports") + ) ) 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) + exists(string m | + nd = MkModuleImport(m) and + ref = DataFlow::moduleImport(m) ) or - exists(string moduleName, string exportName | - base = MkTypeUse(moduleName, exportName) and - lbl = Label::instance() and - ref.(DataFlow::SourceNode).hasUnderlyingType(moduleName, exportName) + exists(DataFlow::ClassNode cls | nd = MkClassInstance(cls) | + ref = cls.getAReceiverNode() + or + ref = cls.(DataFlow::ClassNode).getAPrototypeReference() ) or - exists(DataFlow::InvokeNode call | - base = MkSyntheticCallbackArg(call) and - lbl = Label::parameter(1) and - ref = awaited(call) + use(_, _, ref) and + nd = MkUse(ref) + } + + 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 `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 - // 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) + 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 - decoratorDualEdge(base, lbl, ref) + exists(DataFlow::SourceNode mid | + mid = trackUseNode(nd, promisified, boundArgs, prop, t) and + AdditionalUseStep::step(pragma[only_bind_out](mid), result) + ) or - decoratorUseEdge(base, lbl, ref) + 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 - // 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()) - ) - } + t = useStep(nd, promisified, boundArgs, prop, result) + } - /** - * 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()) - ) - } + /** + * 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) + ) + } - /** 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) - ) - } + private DataFlow::SourceNode trackUseNode( + DataFlow::SourceNode nd, boolean promisified, int boundArgs, string prop + ) { + result = trackUseNode(nd, promisified, boundArgs, prop, DataFlow::TypeTracker::end()) + } - /** 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()) - ) - } + /** + * 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, "") + } - /** - * 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 + private DataFlow::SourceNode trackDefNode(DataFlow::Node nd, DataFlow::TypeBackTracker t) { + t.start() and + rhs(_, nd) and + result = nd.getALocalSource() 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()) + // 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 - not fieldLike.isStatic() and - ref = cls.getAnInstanceReference().getAPropertyReference(fieldLike.getName()) + result = DataFlow::moduleVarNode(imp.getImportedModule()).getAPropertyRead("exports") ) - ) - } - - /** - * 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(ObjectExpr obj | + obj = trackDefNode(nd, t.continue()).asExpr() and + result = + obj.getAProperty() + .(SpreadProperty) + .getInit() + .(SpreadElement) + .getOperand() + .flow() + .getALocalSource() ) - ) - 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 - use(_, _, ref) and - nd = MkUse(ref) - } - - 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 `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) - } - - /** - * 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() - ) - } + t = defStep(nd, result) + } - private DataFlow::SourceNode trackUseNode( - DataFlow::SourceNode nd, boolean promisified, int boundArgs, string prop - ) { - result = trackUseNode(nd, promisified, boundArgs, prop, DataFlow::TypeTracker::end()) - } + /** + * 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) + ) + } - /** - * 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, "") - } + /** + * Gets a node that inter-procedurally flows into `nd`, which is a definition of some node. + */ + cached + DataFlow::SourceNode trackDefNode(DataFlow::Node nd) { + result = trackDefNode(nd, DataFlow::TypeBackTracker::end()) + } - private DataFlow::SourceNode 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()) + private DataFlow::SourceNode awaited(DataFlow::InvokeNode call, DataFlow::TypeTracker t) { + t.startInPromise() and + trackUseNode(_, true, _, "").flowsTo(call.getCalleeNode()) and + result = call 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) - } - - /** - * 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() - ) - } - - /** - * Gets a node that inter-procedurally flows into `nd`, which is a definition of some node. - */ - cached - DataFlow::SourceNode trackDefNode(DataFlow::Node nd) { - result = trackDefNode(nd, DataFlow::TypeBackTracker::end()) - } - - private DataFlow::SourceNode 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)) - } + exists(DataFlow::TypeTracker t2 | result = awaited(call, t2).track(t2, t)) + } - /** - * Gets a node holding the resolved value of promise `call`. - */ - private DataFlow::Node awaited(DataFlow::InvokeNode call) { - result = awaited(call, DataFlow::TypeTracker::end()) - } + /** + * 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`. - */ - 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) + /** + * Holds if there is an edge from `pred` to `succ` in the API graph that is labeled with `lbl`. + */ + 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) + or + succ = MkModuleUse(m) + ) or - succ = MkModuleUse(m) - ) - or - exists(string m | - pred = MkModuleDef(m) and - lbl = Label::member("exports") and - succ = MkModuleExport(m) + exists(string m | + pred = MkModuleDef(m) and + lbl = Label::member("exports") and + succ = MkModuleExport(m) + or + pred = MkModuleUse(m) and + lbl = Label::member("exports") and + succ = MkModuleImport(m) + ) or - 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) + exists(DataFlow::SourceNode ref | + use(pred, lbl, ref) and + succ = MkUse(ref) + ) or - exists(DataFlow::ClassNode cls | - cls.getAnInstanceReference().flowsTo(rhs) and - succ = MkClassInstance(cls) + exists(DataFlow::Node rhs | rhs(pred, lbl, rhs) | + succ = MkDef(rhs) + 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) - ) + 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. + */ + cached + 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) + ) + } } + import Stage + /** * Holds if there is an edge from `pred` to `succ` in the API graph. */ @@ -1506,19 +1508,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 - trackUseNode(src, true, bound, "").flowsTo(result.getCalleeNode()) and - succ = Impl::MkSyntheticCallbackArg(result) - ) - } } /** From a59e280a9e3a87fca2f54ca0555cf5a9738f2878 Mon Sep 17 00:00:00 2001 From: Asger F Date: Thu, 9 Oct 2025 14:07:09 +0200 Subject: [PATCH 06/41] JS: Parameterise the module (still only one instantiation) --- .../ql/lib/semmle/javascript/ApiGraphs.qll | 99 +++++++++++++------ 1 file changed, 71 insertions(+), 28 deletions(-) diff --git a/javascript/ql/lib/semmle/javascript/ApiGraphs.qll b/javascript/ql/lib/semmle/javascript/ApiGraphs.qll index 9afc418c022b..88e6218aa43d 100644 --- a/javascript/ql/lib/semmle/javascript/ApiGraphs.qll +++ b/javascript/ql/lib/semmle/javascript/ApiGraphs.qll @@ -784,17 +784,6 @@ module API { } or MkSyntheticCallbackArg(DataFlow::InvokeNode nd) - private predicate needsDefNode(DataFlow::ClassNode cls) { - hasSemantics(cls) and - ( - cls = trackDefNode(_) - or - cls.getAnInstanceReference() = trackDefNode(_) - or - needsDefNode(cls.getADirectSubClass()) - ) - } - class TDef = MkModuleDef or TNonModuleDef; class TNonModuleDef = MkModuleExport or MkClassInstance or MkDef or MkSyntheticCallbackArg; @@ -811,8 +800,26 @@ module API { hasSemantics(imp) } + 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 considered "in scope" for 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 out of scope but can be tracked to a node in scope. + * 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 inScope(DataFlow::Node node); + } + cached - private module Stage { + 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. @@ -1005,9 +1012,11 @@ module API { */ cached predicate rhs(TApiNode nd, DataFlow::Node rhs) { + (S::inScope(rhs) or S::isAdditionalDefRoot(nd)) and exists(string m | nd = MkModuleExport(m) | exports(m, rhs)) or rhs(_, _, rhs) and + S::inScope(rhs) and nd = MkDef(rhs) } @@ -1058,7 +1067,8 @@ module API { base = MkRoot() and exists(EntryPoint e | lbl = Label::entryPoint(e) and - ref = e.getASource() + ref = e.getASource() and + S::inScope(ref) ) or // property reads @@ -1230,35 +1240,57 @@ module API { ) } + private predicate needsDefNode(DataFlow::ClassNode cls) { + hasSemantics(cls) and + ( + cls = trackDefNode(_) + or + cls.getAnInstanceReference() = trackDefNode(_) + or + needsDefNode(cls.getADirectSubClass()) + or + 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`. */ 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) + (S::inScope(ref) or S::isAdditionalUseRoot(nd)) and + ( + exists(string m, Module mod | nd = MkModuleDef(m) and mod = importableModule(m) | + ref = DataFlow::moduleVarNode(mod) + ) or - exists(DataFlow::Node base | use(MkModuleDef(m), base) | - ref = trackUseNode(base).getAPropertyRead("exports") + 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(string m | + nd = MkModuleImport(m) and + ref = DataFlow::moduleImport(m) ) ) or - exists(string m | - nd = MkModuleImport(m) and - ref = DataFlow::moduleImport(m) - ) - or - exists(DataFlow::ClassNode cls | nd = MkClassInstance(cls) | + exists(DataFlow::ClassNode cls | nd = MkClassInstance(cls) and needsDefNode(cls) | ref = cls.getAReceiverNode() or ref = cls.(DataFlow::ClassNode).getAPrototypeReference() ) or use(_, _, ref) and + S::inScope(ref) and + nd = MkUse(ref) + or + S::isAdditionalUseRoot(nd) and nd = MkUse(ref) } @@ -1498,7 +1530,18 @@ module API { } } - import Stage + private module Stage1Input implements StageInputSig { + pragma[inline] + predicate isAdditionalUseRoot(Node node) { none() } + + pragma[inline] + predicate isAdditionalDefRoot(Node node) { none() } + + bindingset[node] + predicate inScope(DataFlow::Node node) { any() } + } + + import Stage /** * Holds if there is an edge from `pred` to `succ` in the API graph. From fed9be903fabc1518fe665f63e90fb4f008dbe01 Mon Sep 17 00:00:00 2001 From: Asger F Date: Thu, 9 Oct 2025 15:17:36 +0200 Subject: [PATCH 07/41] JS: Moving 'cache' annotations outside the parameterised module --- .../ql/lib/semmle/javascript/ApiGraphs.qll | 38 +++++++++++++++---- 1 file changed, 30 insertions(+), 8 deletions(-) diff --git a/javascript/ql/lib/semmle/javascript/ApiGraphs.qll b/javascript/ql/lib/semmle/javascript/ApiGraphs.qll index 88e6218aa43d..62ee4053561e 100644 --- a/javascript/ql/lib/semmle/javascript/ApiGraphs.qll +++ b/javascript/ql/lib/semmle/javascript/ApiGraphs.qll @@ -818,7 +818,6 @@ module API { predicate inScope(DataFlow::Node node); } - cached private module Stage { /** * Holds if `rhs` is the right-hand side of a definition of a node that should have an @@ -1010,7 +1009,6 @@ module API { /** * Holds if `rhs` is the right-hand side of a definition of node `nd`. */ - cached predicate rhs(TApiNode nd, DataFlow::Node rhs) { (S::inScope(rhs) or S::isAdditionalDefRoot(nd)) and exists(string m | nd = MkModuleExport(m) | exports(m, rhs)) @@ -1258,7 +1256,6 @@ module API { /** * Holds if `ref` is a use of node `nd`. */ - cached predicate use(TApiNode nd, DataFlow::Node ref) { (S::inScope(ref) or S::isAdditionalUseRoot(nd)) and ( @@ -1380,7 +1377,6 @@ module API { /** * 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, "") } @@ -1431,7 +1427,6 @@ module API { /** * 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()) } @@ -1454,7 +1449,6 @@ module API { /** * Holds if there is an edge from `pred` to `succ` in the API graph that is labeled with `lbl`. */ - cached predicate edge(TApiNode pred, Label::ApiLabel lbl, TApiNode succ) { Stages::ApiStage::ref() and exists(string m | @@ -1520,7 +1514,6 @@ module API { * 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 | use(callee, src) and @@ -1541,7 +1534,36 @@ module API { predicate inScope(DataFlow::Node node) { any() } } - import Stage + private module Stage1 = Stage; + + cached + private module Cached { + cached + predicate rhs(TApiNode nd, DataFlow::Node rhs) { Stage1::rhs(nd, rhs) } + + cached + predicate use(TApiNode nd, DataFlow::Node ref) { Stage1::use(nd, ref) } + + cached + DataFlow::SourceNode trackUseNode(DataFlow::SourceNode nd) { + result = Stage1::trackUseNode(nd) + } + + cached + DataFlow::SourceNode trackDefNode(DataFlow::Node nd) { result = Stage1::trackDefNode(nd) } + + cached + predicate edge(TApiNode pred, Label::ApiLabel lbl, TApiNode succ) { + Stage1::edge(pred, lbl, succ) + } + + cached + DataFlow::InvokeNode getAPromisifiedInvocation(TApiNode callee, int bound, TApiNode succ) { + result = Stage1::getAPromisifiedInvocation(callee, bound, succ) + } + } + + import Cached /** * Holds if there is an edge from `pred` to `succ` in the API graph. From 8eb33cc09c2a09aff4623a5a831be70a457bada2 Mon Sep 17 00:00:00 2001 From: Asger F Date: Fri, 10 Oct 2025 10:01:25 +0200 Subject: [PATCH 08/41] JS: Localize charpred of API::EntryPoint This is needed for localizing ApiLabel later --- javascript/ql/lib/semmle/javascript/ApiGraphs.qll | 4 ++++ .../ql/lib/semmle/javascript/frameworks/ClientRequests.qll | 1 + javascript/ql/lib/semmle/javascript/frameworks/D3.qll | 1 + javascript/ql/lib/semmle/javascript/frameworks/Electron.qll | 1 + javascript/ql/lib/semmle/javascript/frameworks/History.qll | 1 + javascript/ql/lib/semmle/javascript/frameworks/Immutable.qll | 1 + javascript/ql/lib/semmle/javascript/frameworks/Logging.qll | 1 + javascript/ql/lib/semmle/javascript/frameworks/Nest.qll | 1 + javascript/ql/lib/semmle/javascript/frameworks/Redux.qll | 1 + javascript/ql/lib/semmle/javascript/frameworks/Templating.qll | 2 +- .../ql/lib/semmle/javascript/frameworks/TrustedTypes.qll | 1 + javascript/ql/lib/semmle/javascript/frameworks/Vue.qll | 3 +++ .../ql/lib/semmle/javascript/frameworks/WebResponse.qll | 2 ++ javascript/ql/lib/semmle/javascript/frameworks/WebSocket.qll | 2 ++ javascript/ql/lib/semmle/javascript/frameworks/Webix.qll | 1 + .../javascript/frameworks/data/internal/ApiGraphModels.qll | 2 ++ .../frameworks/data/internal/ApiGraphModelsSpecific.qll | 2 ++ .../semmle/javascript/security/dataflow/RemoteFlowSources.qll | 2 ++ .../ql/test/ApiGraphs/custom-entry-point/VerifyAssertions.ql | 1 + 19 files changed, 29 insertions(+), 1 deletion(-) diff --git a/javascript/ql/lib/semmle/javascript/ApiGraphs.qll b/javascript/ql/lib/semmle/javascript/ApiGraphs.qll index 62ee4053561e..3f320cec59ad 100644 --- a/javascript/ql/lib/semmle/javascript/ApiGraphs.qll +++ b/javascript/ql/lib/semmle/javascript/ApiGraphs.qll @@ -676,17 +676,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)) } } 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/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/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/Templating.qll b/javascript/ql/lib/semmle/javascript/frameworks/Templating.qll index f1f91785329c..8932c2753ab2 100644 --- a/javascript/ql/lib/semmle/javascript/frameworks/Templating.qll +++ b/javascript/ql/lib/semmle/javascript/frameworks/Templating.qll @@ -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/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" } From 38f1436ecacdf8d4a0c6698d07120176876d1ab7 Mon Sep 17 00:00:00 2001 From: Asger F Date: Fri, 10 Oct 2025 10:02:35 +0200 Subject: [PATCH 09/41] JS: Localize MkModuleExport --- .../ql/lib/semmle/javascript/ApiGraphs.qll | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/javascript/ql/lib/semmle/javascript/ApiGraphs.qll b/javascript/ql/lib/semmle/javascript/ApiGraphs.qll index 3f320cec59ad..9a3ba76a487e 100644 --- a/javascript/ql/lib/semmle/javascript/ApiGraphs.qll +++ b/javascript/ql/lib/semmle/javascript/ApiGraphs.qll @@ -740,20 +740,9 @@ module API { 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 or any(TypeAnnotation n).hasUnderlyingType(m, _) } or @@ -1965,3 +1954,8 @@ private Module importableModule(string m) { m = pkg.getPackageName() ) } + +overlay[local] +private predicate isDeclaredPackageName(string m) { + m = any(PackageJson pkg).getDeclaredPackageName() +} From 3c0d8bb03a231b86ab47a51e42a200539d237ae5 Mon Sep 17 00:00:00 2001 From: Asger F Date: Fri, 10 Oct 2025 10:03:03 +0200 Subject: [PATCH 10/41] JS: Localize MkModuleImport --- javascript/ql/lib/semmle/javascript/ApiGraphs.qll | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/javascript/ql/lib/semmle/javascript/ApiGraphs.qll b/javascript/ql/lib/semmle/javascript/ApiGraphs.qll index 9a3ba76a487e..827ea3335b03 100644 --- a/javascript/ql/lib/semmle/javascript/ApiGraphs.qll +++ b/javascript/ql/lib/semmle/javascript/ApiGraphs.qll @@ -740,9 +740,8 @@ module API { MkRoot() or MkModuleDef(string m) { exists(MkModuleExport(m)) } or MkModuleUse(string m) { exists(MkModuleImport(m)) } or - MkModuleImport(string m) { - imports(_, m) MkModuleExport(string m) { isDeclaredPackageName(m) } or + MkModuleImport(string m) { isImportedPackageName(m) } or or any(TypeAnnotation n).hasUnderlyingType(m, _) } or @@ -1959,3 +1958,9 @@ overlay[local] private predicate isDeclaredPackageName(string m) { m = any(PackageJson pkg).getDeclaredPackageName() } + +overlay[local] +private predicate isImportedPackageName(string m) { + m = any(Import imprt).getImportedPathString() and + m.regexpMatch("[^./].*") +} From c0f27b2b0ff162496e36733009e0cf8ec11786ad Mon Sep 17 00:00:00 2001 From: Asger F Date: Fri, 10 Oct 2025 10:03:21 +0200 Subject: [PATCH 11/41] JS: Localize MkClassInstance --- javascript/ql/lib/semmle/javascript/ApiGraphs.qll | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/javascript/ql/lib/semmle/javascript/ApiGraphs.qll b/javascript/ql/lib/semmle/javascript/ApiGraphs.qll index 827ea3335b03..f82ffe21542e 100644 --- a/javascript/ql/lib/semmle/javascript/ApiGraphs.qll +++ b/javascript/ql/lib/semmle/javascript/ApiGraphs.qll @@ -742,10 +742,11 @@ module API { MkModuleUse(string m) { exists(MkModuleImport(m)) } or 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, _) + cls = any(ClassDefinition c).flow() } or - MkClassInstance(DataFlow::ClassNode cls) or MkDef(DataFlow::Node nd) { nd = any(DataFlow::PropWrite w).getRhs() or From 17b0bcc2343abb598c9ec48979c5986effabf391 Mon Sep 17 00:00:00 2001 From: Asger F Date: Fri, 10 Oct 2025 10:04:44 +0200 Subject: [PATCH 12/41] JS: Use forceLocal to localize MkTypeUse --- javascript/ql/lib/semmle/javascript/ApiGraphs.qll | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/javascript/ql/lib/semmle/javascript/ApiGraphs.qll b/javascript/ql/lib/semmle/javascript/ApiGraphs.qll index f82ffe21542e..03bce9871acb 100644 --- a/javascript/ql/lib/semmle/javascript/ApiGraphs.qll +++ b/javascript/ql/lib/semmle/javascript/ApiGraphs.qll @@ -735,6 +735,14 @@ 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) + cached newtype TApiNode = MkRoot() or @@ -772,9 +780,7 @@ module API { } or MkUse(DataFlow::Node nd) { nd instanceof DataFlow::SourceNode } or /** A use of a TypeScript type. */ - MkTypeUse(string moduleName, string exportName) { - any(TypeAnnotation n).hasUnderlyingType(moduleName, exportName) - } or + MkTypeUse(string moduleName, string exportName) { hasTypeUseLocal(moduleName, exportName) } or MkSyntheticCallbackArg(DataFlow::InvokeNode nd) class TDef = MkModuleDef or TNonModuleDef; From 329cb2808909189da71e9502a496646bbec4dc92 Mon Sep 17 00:00:00 2001 From: Asger F Date: Fri, 10 Oct 2025 10:05:08 +0200 Subject: [PATCH 13/41] JS: Remove unused predicate --- javascript/ql/lib/semmle/javascript/ApiGraphs.qll | 8 -------- 1 file changed, 8 deletions(-) diff --git a/javascript/ql/lib/semmle/javascript/ApiGraphs.qll b/javascript/ql/lib/semmle/javascript/ApiGraphs.qll index 03bce9871acb..7334d151df59 100644 --- a/javascript/ql/lib/semmle/javascript/ApiGraphs.qll +++ b/javascript/ql/lib/semmle/javascript/ApiGraphs.qll @@ -791,14 +791,6 @@ 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) - } - 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); From 93333c0c0c35067dd81500a116d69ee4a5bf723f Mon Sep 17 00:00:00 2001 From: Asger F Date: Fri, 10 Oct 2025 10:15:10 +0200 Subject: [PATCH 14/41] JS: Make API nodes and labels local --- .../ql/lib/semmle/javascript/ApiGraphs.qll | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/javascript/ql/lib/semmle/javascript/ApiGraphs.qll b/javascript/ql/lib/semmle/javascript/ApiGraphs.qll index 7334d151df59..fa1cd285e252 100644 --- a/javascript/ql/lib/semmle/javascript/ApiGraphs.qll +++ b/javascript/ql/lib/semmle/javascript/ApiGraphs.qll @@ -743,6 +743,7 @@ module API { private predicate hasTypeUseLocal(string moduleName, string exportName) = forceLocal(hasTypeUse/2)(moduleName, exportName) + overlay[local] cached newtype TApiNode = MkRoot() or @@ -1623,6 +1624,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 { @@ -1661,6 +1663,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 @@ -1671,16 +1674,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 @@ -1747,9 +1753,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 + AccessPath::isAssignedInUniqueFile(name) + or + exists(AccessPath::getAnAssignmentTo(_, name)) + or + name = DataFlow::PseudoProperties::arrayLikeElement() or - exists(Impl::MkTypeUse(_, name)) + name = any(TypeAccess t).getIdentifier().getName() } or MkLabelParameter(int i) { i = From 6a1559423c5be64b1bddae74998fd5c05b3614dc Mon Sep 17 00:00:00 2001 From: Asger F Date: Fri, 28 Nov 2025 10:12:52 +0100 Subject: [PATCH 15/41] JS: Improve join order at MkUse call --- javascript/ql/lib/semmle/javascript/ApiGraphs.qll | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/javascript/ql/lib/semmle/javascript/ApiGraphs.qll b/javascript/ql/lib/semmle/javascript/ApiGraphs.qll index fa1cd285e252..a78c23a8f9bb 100644 --- a/javascript/ql/lib/semmle/javascript/ApiGraphs.qll +++ b/javascript/ql/lib/semmle/javascript/ApiGraphs.qll @@ -1280,9 +1280,13 @@ module API { nd = MkUse(ref) or S::isAdditionalUseRoot(nd) and - nd = MkUse(ref) + nd = mkUseLate(ref) } + bindingset[node] + pragma[inline_late] + private TApiNode mkUseLate(DataFlow::Node node) { result = MkUse(node) } + private import semmle.javascript.dataflow.TypeTracking /** From 85ee446ad8251cb309c0fb465baad049eefe9de2 Mon Sep 17 00:00:00 2001 From: Asger F Date: Fri, 28 Nov 2025 10:13:13 +0100 Subject: [PATCH 16/41] JS: Add missing def-node roots --- javascript/ql/lib/semmle/javascript/ApiGraphs.qll | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/javascript/ql/lib/semmle/javascript/ApiGraphs.qll b/javascript/ql/lib/semmle/javascript/ApiGraphs.qll index a78c23a8f9bb..0dca3c1dd8e2 100644 --- a/javascript/ql/lib/semmle/javascript/ApiGraphs.qll +++ b/javascript/ql/lib/semmle/javascript/ApiGraphs.qll @@ -1008,6 +1008,9 @@ module API { rhs(_, _, rhs) and S::inScope(rhs) and nd = MkDef(rhs) + or + S::isAdditionalDefRoot(nd) and + nd = mkDefLate(rhs) } /** @@ -1287,6 +1290,10 @@ module API { 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 /** From 9c7d636787e853479f5df298f8634dea08e52dbb Mon Sep 17 00:00:00 2001 From: Asger F Date: Fri, 28 Nov 2025 10:15:58 +0100 Subject: [PATCH 17/41] JS: Call forceLocal on the output of Stage 1 --- .../ql/lib/semmle/javascript/ApiGraphs.qll | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/javascript/ql/lib/semmle/javascript/ApiGraphs.qll b/javascript/ql/lib/semmle/javascript/ApiGraphs.qll index 0dca3c1dd8e2..488fddc95de8 100644 --- a/javascript/ql/lib/semmle/javascript/ApiGraphs.qll +++ b/javascript/ql/lib/semmle/javascript/ApiGraphs.qll @@ -1539,6 +1539,25 @@ module API { private module Stage1 = Stage; + overlay[local] + private module Stage1Local { + predicate use(TApiNode node, DataFlow::Node ref) = forceLocal(Stage1::use/2)(node, ref) + + predicate rhs(TApiNode node, DataFlow::Node def) = forceLocal(Stage1::rhs/2)(node, def) + + DataFlow::SourceNode trackUseNode(DataFlow::SourceNode nd) = + forceLocal(Stage1::trackUseNode/1)(nd, result) + + DataFlow::SourceNode trackDefNode(DataFlow::SourceNode nd) = + forceLocal(Stage1::trackDefNode/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) + } + cached private module Cached { cached From 00a4964b3a06ea014062219a6ee00d7601fc004b Mon Sep 17 00:00:00 2001 From: Asger F Date: Fri, 28 Nov 2025 10:16:20 +0100 Subject: [PATCH 18/41] JS: Also expose "any state" version of tracking predicates --- .../ql/lib/semmle/javascript/ApiGraphs.qll | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/javascript/ql/lib/semmle/javascript/ApiGraphs.qll b/javascript/ql/lib/semmle/javascript/ApiGraphs.qll index 488fddc95de8..f0ce21758f5f 100644 --- a/javascript/ql/lib/semmle/javascript/ApiGraphs.qll +++ b/javascript/ql/lib/semmle/javascript/ApiGraphs.qll @@ -1384,6 +1384,13 @@ module API { 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 @@ -1434,6 +1441,11 @@ module API { result = trackDefNode(nd, DataFlow::TypeBackTracker::end()) } + /** + * 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, _) } + private DataFlow::SourceNode awaited(DataFlow::InvokeNode call, DataFlow::TypeTracker t) { t.startInPromise() and trackUseNode(_, true, _, "").flowsTo(call.getCalleeNode()) and @@ -1548,9 +1560,15 @@ module API { DataFlow::SourceNode trackUseNode(DataFlow::SourceNode nd) = forceLocal(Stage1::trackUseNode/1)(nd, result) + DataFlow::SourceNode trackUseNodeAnyState(DataFlow::SourceNode nd) = + forceLocal(Stage1::trackUseNodeAnyState/1)(nd, result) + DataFlow::SourceNode trackDefNode(DataFlow::SourceNode 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) From 1e50800920c52bb4b3d51d8b369f5cda50a283c2 Mon Sep 17 00:00:00 2001 From: Asger F Date: Fri, 28 Nov 2025 10:18:46 +0100 Subject: [PATCH 19/41] JS: Restrict Stage1 to the base database --- javascript/ql/lib/semmle/javascript/ApiGraphs.qll | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/javascript/ql/lib/semmle/javascript/ApiGraphs.qll b/javascript/ql/lib/semmle/javascript/ApiGraphs.qll index f0ce21758f5f..0381c4db8f10 100644 --- a/javascript/ql/lib/semmle/javascript/ApiGraphs.qll +++ b/javascript/ql/lib/semmle/javascript/ApiGraphs.qll @@ -1545,8 +1545,15 @@ module API { pragma[inline] predicate isAdditionalDefRoot(Node node) { none() } + overlay[local] + private predicate isOverlay() { databaseMetadata("isOverlay", "true") } + bindingset[node] - predicate inScope(DataFlow::Node node) { any() } + predicate inScope(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) + } } private module Stage1 = Stage; From 965f4337352e8649407819545a80160353544b16 Mon Sep 17 00:00:00 2001 From: Asger F Date: Fri, 28 Nov 2025 10:19:06 +0100 Subject: [PATCH 20/41] JS: Add overlay-specific Stage2 --- .../ql/lib/semmle/javascript/ApiGraphs.qll | 106 +++++++++++++++++- 1 file changed, 100 insertions(+), 6 deletions(-) diff --git a/javascript/ql/lib/semmle/javascript/ApiGraphs.qll b/javascript/ql/lib/semmle/javascript/ApiGraphs.qll index 0381c4db8f10..733b90319633 100644 --- a/javascript/ql/lib/semmle/javascript/ApiGraphs.qll +++ b/javascript/ql/lib/semmle/javascript/ApiGraphs.qll @@ -792,6 +792,10 @@ module API { private predicate hasSemantics(DataFlow::Node nd) { not nd.getTopLevel().isExterns() } + 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); @@ -1583,30 +1587,120 @@ module API { forceLocal(Stage1::getAPromisifiedInvocation/3)(callee, bound, succ, result) } + 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 inScope(DataFlow::Node node) { isInOverlayChangedFile(node) } + } + + private module Stage2 = Stage; + cached private module Cached { cached - predicate rhs(TApiNode nd, DataFlow::Node rhs) { Stage1::rhs(nd, rhs) } + predicate rhs(TApiNode nd, DataFlow::Node rhs) { + Stage1Local::rhs(nd, rhs) + or + Stage2::rhs(nd, rhs) + } cached - predicate use(TApiNode nd, DataFlow::Node ref) { Stage1::use(nd, ref) } + predicate use(TApiNode nd, DataFlow::Node ref) { + Stage1Local::use(nd, ref) + or + Stage2::use(nd, ref) + } cached DataFlow::SourceNode trackUseNode(DataFlow::SourceNode nd) { - result = Stage1::trackUseNode(nd) + result = Stage1Local::trackUseNode(nd) + or + result = Stage2::trackUseNode(nd) } cached - DataFlow::SourceNode trackDefNode(DataFlow::Node nd) { result = Stage1::trackDefNode(nd) } + DataFlow::SourceNode trackDefNode(DataFlow::Node nd) { + result = Stage1Local::trackDefNode(nd) + or + result = Stage2::trackDefNode(nd) + } cached predicate edge(TApiNode pred, Label::ApiLabel lbl, TApiNode succ) { - Stage1::edge(pred, lbl, succ) + Stage1Local::edge(pred, lbl, succ) + or + Stage2::edge(pred, lbl, succ) } cached DataFlow::InvokeNode getAPromisifiedInvocation(TApiNode callee, int bound, TApiNode succ) { - result = Stage1::getAPromisifiedInvocation(callee, bound, succ) + result = Stage1Local::getAPromisifiedInvocation(callee, bound, succ) + or + result = Stage2::getAPromisifiedInvocation(callee, bound, succ) } } From a54f881d7a385f6a27f4e932e98aa76929d08f9d Mon Sep 17 00:00:00 2001 From: Asger F Date: Fri, 10 Oct 2025 10:42:12 +0200 Subject: [PATCH 21/41] JS:Add more member labels --- javascript/ql/lib/semmle/javascript/ApiGraphs.qll | 2 ++ 1 file changed, 2 insertions(+) diff --git a/javascript/ql/lib/semmle/javascript/ApiGraphs.qll b/javascript/ql/lib/semmle/javascript/ApiGraphs.qll index 733b90319633..842feffad34c 100644 --- a/javascript/ql/lib/semmle/javascript/ApiGraphs.qll +++ b/javascript/ql/lib/semmle/javascript/ApiGraphs.qll @@ -1913,6 +1913,8 @@ module API { name = DataFlow::PseudoProperties::arrayLikeElement() or name = any(TypeAccess t).getIdentifier().getName() + or + name = any(Expr s).getStringValue() } or MkLabelParameter(int i) { i = From 79a751ceb0329ba2fb0f34a9ca1c59de8a1e55e0 Mon Sep 17 00:00:00 2001 From: Asger F Date: Fri, 10 Oct 2025 10:42:22 +0200 Subject: [PATCH 22/41] JS: Add debug tools for detecting lost nodes/edges --- .../ql/lib/semmle/javascript/ApiGraphs.qll | 41 ++++++++++++++++++- 1 file changed, 39 insertions(+), 2 deletions(-) diff --git a/javascript/ql/lib/semmle/javascript/ApiGraphs.qll b/javascript/ql/lib/semmle/javascript/ApiGraphs.qll index 842feffad34c..112c3d7cf7f6 100644 --- a/javascript/ql/lib/semmle/javascript/ApiGraphs.qll +++ b/javascript/ql/lib/semmle/javascript/ApiGraphs.qll @@ -819,7 +819,7 @@ module API { * 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. */ - private predicate rhs(TApiNode base, Label::ApiLabel lbl, DataFlow::Node rhs) { + predicate rhs(TApiNode base, Label::ApiLabel lbl, DataFlow::Node rhs) { hasSemantics(rhs) and ( base = MkRoot() and @@ -1058,7 +1058,7 @@ module API { * Holds if `ref` is a use of a node that should have an incoming edge from `base` labeled * `lbl` in the API graph. */ - private predicate use(TApiNode base, Label::ApiLabel lbl, DataFlow::Node ref) { + predicate use(TApiNode base, Label::ApiLabel lbl, DataFlow::Node ref) { hasSemantics(ref) and ( base = MkRoot() and @@ -1706,6 +1706,43 @@ module API { import Cached + private module Debug { + private module FullInput implements StageInputSig { + pragma[inline] + predicate isAdditionalUseRoot(Node node) { none() } + + pragma[inline] + predicate isAdditionalDefRoot(Node node) { none() } + + bindingset[node] + predicate inScope(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) + } + } + /** * Holds if there is an edge from `pred` to `succ` in the API graph. */ From 13fccbe3e1cba7ff6bf14a0d381c8bab043e6e6c Mon Sep 17 00:00:00 2001 From: Asger F Date: Fri, 10 Oct 2025 11:18:27 +0200 Subject: [PATCH 23/41] JS: Change signature of 'edges' to support quick eval --- javascript/ql/lib/semmle/javascript/ApiGraphs.qll | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/javascript/ql/lib/semmle/javascript/ApiGraphs.qll b/javascript/ql/lib/semmle/javascript/ApiGraphs.qll index 112c3d7cf7f6..ec1585df6f59 100644 --- a/javascript/ql/lib/semmle/javascript/ApiGraphs.qll +++ b/javascript/ql/lib/semmle/javascript/ApiGraphs.qll @@ -1690,7 +1690,7 @@ module API { } cached - predicate edge(TApiNode pred, Label::ApiLabel lbl, TApiNode succ) { + predicate edge(Node pred, Label::ApiLabel lbl, Node succ) { Stage1Local::edge(pred, lbl, succ) or Stage2::edge(pred, lbl, succ) From 7b0ee6fcf73abf083fd81f44b19f7c3009d2c81e Mon Sep 17 00:00:00 2001 From: Asger F Date: Mon, 6 Oct 2025 11:39:54 +0200 Subject: [PATCH 24/41] JS: Add back CallReceiverStep() restriction This was initially lost after rebasing with indentation changes --- javascript/ql/lib/semmle/javascript/ApiGraphs.qll | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/javascript/ql/lib/semmle/javascript/ApiGraphs.qll b/javascript/ql/lib/semmle/javascript/ApiGraphs.qll index ec1585df6f59..d802d538f23a 100644 --- a/javascript/ql/lib/semmle/javascript/ApiGraphs.qll +++ b/javascript/ql/lib/semmle/javascript/ApiGraphs.qll @@ -1371,7 +1371,9 @@ module API { 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) + result = t.append(summary) and + // Block argument-passing into 'this' when it determines the call target + not summary = CallReceiverStep() ) } @@ -1434,7 +1436,9 @@ module API { exists(DataFlow::TypeBackTracker t, StepSummary summary, DataFlow::Node next | next = trackDefNode(nd, t) and StepSummary::step(prev, next, summary) and - result = t.prepend(summary) + result = t.prepend(summary) and + // Block argument-passing into 'this' when it determines the call target + not summary = CallReceiverStep() ) } From eecfe15e37cfb7d68fd0b7084bcc9bd0281ce696 Mon Sep 17 00:00:00 2001 From: Asger F Date: Mon, 3 Nov 2025 11:52:06 +0100 Subject: [PATCH 25/41] JS: Remove global dependency that wasnt needed anyway --- javascript/ql/lib/semmle/javascript/ApiGraphs.qll | 2 -- 1 file changed, 2 deletions(-) diff --git a/javascript/ql/lib/semmle/javascript/ApiGraphs.qll b/javascript/ql/lib/semmle/javascript/ApiGraphs.qll index d802d538f23a..8e8881b8950e 100644 --- a/javascript/ql/lib/semmle/javascript/ApiGraphs.qll +++ b/javascript/ql/lib/semmle/javascript/ApiGraphs.qll @@ -1947,8 +1947,6 @@ module API { or name = any(DataFlow::PropRef pr).getPropertyName() or - AccessPath::isAssignedInUniqueFile(name) - or exists(AccessPath::getAnAssignmentTo(_, name)) or name = DataFlow::PseudoProperties::arrayLikeElement() From 4cc307810527bfbc23d21b25c8b37e17cf7653fd Mon Sep 17 00:00:00 2001 From: Asger F Date: Fri, 10 Oct 2025 21:37:01 +0200 Subject: [PATCH 26/41] JS: Add overlay[global] to abstract classes with fields Some abstract classes defines fields without binding them, leaving it up to the subclasses to bind them. When combined with overlay[local?], the charpred for such an abstract class can become local, while the subclasses are global. The means the charpred needs to be materialized, even though it doesn't bind the fields, leading to a cartesian product. --- javascript/ql/lib/semmle/javascript/DOM.qll | 1 + javascript/ql/lib/semmle/javascript/frameworks/EventEmitter.qll | 2 ++ javascript/ql/lib/semmle/javascript/frameworks/NodeJSLib.qll | 1 + javascript/ql/lib/semmle/javascript/frameworks/SQL.qll | 1 + .../dataflow/SecondOrderCommandInjectionCustomizations.qll | 1 + .../javascript/security/dataflow/TaintedPathCustomizations.qll | 1 + .../security/dataflow/UnsafeHtmlConstructionCustomizations.qll | 1 + 7 files changed, 8 insertions(+) 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/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/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/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/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; From 11bba6871e5e00d22255f6b45288f617c8c0ea34 Mon Sep 17 00:00:00 2001 From: Asger F Date: Thu, 20 Nov 2025 16:04:18 +0100 Subject: [PATCH 27/41] JS: Include import paths from custom ModuleImportNode::Range subclasses --- javascript/ql/lib/semmle/javascript/ApiGraphs.qll | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/javascript/ql/lib/semmle/javascript/ApiGraphs.qll b/javascript/ql/lib/semmle/javascript/ApiGraphs.qll index 8e8881b8950e..45efee5f3c0d 100644 --- a/javascript/ql/lib/semmle/javascript/ApiGraphs.qll +++ b/javascript/ql/lib/semmle/javascript/ApiGraphs.qll @@ -2164,6 +2164,10 @@ private predicate isDeclaredPackageName(string m) { overlay[local] private predicate isImportedPackageName(string m) { - m = any(Import imprt).getImportedPathString() and + ( + m = any(Import imprt).getImportedPathString() + or + m = any(DataFlow::ModuleImportNode im).getPath() + ) and m.regexpMatch("[^./].*") } From 98bf9cec92a1ddf27a9b8c61fbbf41de8428e9ec Mon Sep 17 00:00:00 2001 From: Asger F Date: Mon, 24 Nov 2025 11:46:38 +0100 Subject: [PATCH 28/41] JS: Bugfix in Stage1Local::trackDefNode --- javascript/ql/lib/semmle/javascript/ApiGraphs.qll | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/javascript/ql/lib/semmle/javascript/ApiGraphs.qll b/javascript/ql/lib/semmle/javascript/ApiGraphs.qll index 45efee5f3c0d..b952578f5998 100644 --- a/javascript/ql/lib/semmle/javascript/ApiGraphs.qll +++ b/javascript/ql/lib/semmle/javascript/ApiGraphs.qll @@ -1578,7 +1578,7 @@ module API { DataFlow::SourceNode trackUseNodeAnyState(DataFlow::SourceNode nd) = forceLocal(Stage1::trackUseNodeAnyState/1)(nd, result) - DataFlow::SourceNode trackDefNode(DataFlow::SourceNode nd) = + DataFlow::SourceNode trackDefNode(DataFlow::Node nd) = forceLocal(Stage1::trackDefNode/1)(nd, result) DataFlow::SourceNode trackDefNodeAnyState(DataFlow::Node nd) = From f94135858cac7e339b9046aa2ec46f50c4b69ee7 Mon Sep 17 00:00:00 2001 From: Asger F Date: Mon, 24 Nov 2025 11:38:09 +0100 Subject: [PATCH 29/41] JS: Simplify toString() --- .../ql/lib/semmle/javascript/ApiGraphs.qll | 38 ++++++++++++++----- 1 file changed, 28 insertions(+), 10 deletions(-) diff --git a/javascript/ql/lib/semmle/javascript/ApiGraphs.qll b/javascript/ql/lib/semmle/javascript/ApiGraphs.qll index b952578f5998..b9e335e781d5 100644 --- a/javascript/ql/lib/semmle/javascript/ApiGraphs.qll +++ b/javascript/ql/lib/semmle/javascript/ApiGraphs.qll @@ -579,7 +579,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 +631,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.getInducingNode().toString() } - } + 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.getInducingNode().toString() } - } + class Use extends Node, Impl::TUse { } /** Gets the root node. */ Root root() { any() } From 807c8b670939c9d109066ac3673247391dac9bbd Mon Sep 17 00:00:00 2001 From: Asger F Date: Mon, 24 Nov 2025 11:46:25 +0100 Subject: [PATCH 30/41] JS: Update test output to reflect Node.toString() change --- .../EndpointNaming/EndpointNaming.expected | 6 +- .../frameworks/Knex/test.expected | 408 +++++++++--------- .../frameworks/Redux/test.expected | 48 +-- 3 files changed, 231 insertions(+), 231 deletions(-) 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 | From 1dfb5a1dd40b615c804bce1e56737e67cd97a0fb Mon Sep 17 00:00:00 2001 From: Asger F Date: Mon, 24 Nov 2025 13:33:03 +0100 Subject: [PATCH 31/41] JS: Work around an issue with overlay-invariance --- javascript/ql/lib/semmle/javascript/frameworks/Templating.qll | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/javascript/ql/lib/semmle/javascript/frameworks/Templating.qll b/javascript/ql/lib/semmle/javascript/frameworks/Templating.qll index 8932c2753ab2..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. */ From 47a6627441dbd18596f0bf5b9da0370f1842c5d3 Mon Sep 17 00:00:00 2001 From: Asger F Date: Mon, 24 Nov 2025 14:12:03 +0100 Subject: [PATCH 32/41] JS: Add back promisify-all support This was somehow lost in a rebase --- javascript/ql/lib/semmle/javascript/ApiGraphs.qll | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/javascript/ql/lib/semmle/javascript/ApiGraphs.qll b/javascript/ql/lib/semmle/javascript/ApiGraphs.qll index b9e335e781d5..cc6f92ba2564 100644 --- a/javascript/ql/lib/semmle/javascript/ApiGraphs.qll +++ b/javascript/ql/lib/semmle/javascript/ApiGraphs.qll @@ -1161,6 +1161,17 @@ module API { 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) From 9751a33d64788b39725df9c806c84334e8e628b9 Mon Sep 17 00:00:00 2001 From: Asger F Date: Mon, 24 Nov 2025 14:35:53 +0100 Subject: [PATCH 33/41] JS: Make API::Node overlay[local?] We want the type itself to be local but nearly all its member predicates are global. --- javascript/ql/lib/semmle/javascript/ApiGraphs.qll | 3 +++ 1 file changed, 3 insertions(+) diff --git a/javascript/ql/lib/semmle/javascript/ApiGraphs.qll b/javascript/ql/lib/semmle/javascript/ApiGraphs.qll index cc6f92ba2564..aee6a0c72195 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. @@ -1576,9 +1577,11 @@ module API { } private module Stage1Input implements StageInputSig { + overlay[caller] pragma[inline] predicate isAdditionalUseRoot(Node node) { none() } + overlay[caller] pragma[inline] predicate isAdditionalDefRoot(Node node) { none() } From f132dd54d7894e62f876e08a0775f6122f646847 Mon Sep 17 00:00:00 2001 From: Asger F Date: Mon, 24 Nov 2025 14:37:51 +0100 Subject: [PATCH 34/41] JS: Sync ApiGraphModels.qll --- .../semmle/python/frameworks/data/internal/ApiGraphModels.qll | 2 ++ .../lib/codeql/ruby/frameworks/data/internal/ApiGraphModels.qll | 2 ++ 2 files changed, 4 insertions(+) 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; From f7517547e7d194fc13923ea64327a7db9d5c86b3 Mon Sep 17 00:00:00 2001 From: Asger F Date: Tue, 25 Nov 2025 11:52:44 +0100 Subject: [PATCH 35/41] JS: Fix some QL4QL alerts --- javascript/ql/lib/semmle/javascript/ApiGraphs.qll | 2 ++ 1 file changed, 2 insertions(+) diff --git a/javascript/ql/lib/semmle/javascript/ApiGraphs.qll b/javascript/ql/lib/semmle/javascript/ApiGraphs.qll index aee6a0c72195..3b0de9863ae0 100644 --- a/javascript/ql/lib/semmle/javascript/ApiGraphs.qll +++ b/javascript/ql/lib/semmle/javascript/ApiGraphs.qll @@ -1744,9 +1744,11 @@ module API { 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() } From 5fbf8770f613ba4d0fa103f02c2e46a546df80b6 Mon Sep 17 00:00:00 2001 From: Asger F Date: Tue, 25 Nov 2025 14:09:13 +0100 Subject: [PATCH 36/41] JS: Add a missing needsDefNode restriction Previously this was implied by MkClassInstance but that's no longer the case. --- javascript/ql/lib/semmle/javascript/ApiGraphs.qll | 1 + 1 file changed, 1 insertion(+) diff --git a/javascript/ql/lib/semmle/javascript/ApiGraphs.qll b/javascript/ql/lib/semmle/javascript/ApiGraphs.qll index 3b0de9863ae0..d1bc4a23348d 100644 --- a/javascript/ql/lib/semmle/javascript/ApiGraphs.qll +++ b/javascript/ql/lib/semmle/javascript/ApiGraphs.qll @@ -1532,6 +1532,7 @@ module API { succ = MkDef(rhs) or exists(DataFlow::ClassNode cls | + needsDefNode(cls) and cls.getAnInstanceReference().flowsTo(rhs) and succ = MkClassInstance(cls) ) From 6b14789d6d6b0246d67dd6ff0f4678afb71c4f23 Mon Sep 17 00:00:00 2001 From: Asger F Date: Tue, 2 Dec 2025 14:42:51 +0100 Subject: [PATCH 37/41] JS: Fix bad join in export logic --- .../lib/semmle/javascript/ES2015Modules.qll | 24 +++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) 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) ) } } From 0a241e22e05d298d6d2983d4dc09a1ab4b1d0be9 Mon Sep 17 00:00:00 2001 From: Asger F Date: Tue, 9 Dec 2025 09:02:55 +0100 Subject: [PATCH 38/41] Update javascript/ql/lib/semmle/javascript/ApiGraphs.qll Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- javascript/ql/lib/semmle/javascript/ApiGraphs.qll | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/javascript/ql/lib/semmle/javascript/ApiGraphs.qll b/javascript/ql/lib/semmle/javascript/ApiGraphs.qll index d1bc4a23348d..1801b0c2e89a 100644 --- a/javascript/ql/lib/semmle/javascript/ApiGraphs.qll +++ b/javascript/ql/lib/semmle/javascript/ApiGraphs.qll @@ -1336,7 +1336,7 @@ module API { * 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; + * - `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. From 4aa1368a15c115f4ec878a335fc4cad63d6cc8be Mon Sep 17 00:00:00 2001 From: Asger F Date: Tue, 9 Dec 2025 09:03:11 +0100 Subject: [PATCH 39/41] Update javascript/ql/lib/semmle/javascript/ApiGraphs.qll Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- javascript/ql/lib/semmle/javascript/ApiGraphs.qll | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/javascript/ql/lib/semmle/javascript/ApiGraphs.qll b/javascript/ql/lib/semmle/javascript/ApiGraphs.qll index 1801b0c2e89a..ed0f0ead9050 100644 --- a/javascript/ql/lib/semmle/javascript/ApiGraphs.qll +++ b/javascript/ql/lib/semmle/javascript/ApiGraphs.qll @@ -1240,7 +1240,7 @@ module API { } /** - * Holds if `ref` is a reference to a field/accessor that should have en incoming edge from base labelled `lbl`. + * 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. From 1bfbe41672c6bea9199ac71c1dda68a62432efbb Mon Sep 17 00:00:00 2001 From: Asger F Date: Wed, 17 Dec 2025 10:59:37 +0100 Subject: [PATCH 40/41] Update javascript/ql/lib/semmle/javascript/ApiGraphs.qll Co-authored-by: Taus --- javascript/ql/lib/semmle/javascript/ApiGraphs.qll | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/javascript/ql/lib/semmle/javascript/ApiGraphs.qll b/javascript/ql/lib/semmle/javascript/ApiGraphs.qll index ed0f0ead9050..fde122ea78b4 100644 --- a/javascript/ql/lib/semmle/javascript/ApiGraphs.qll +++ b/javascript/ql/lib/semmle/javascript/ApiGraphs.qll @@ -824,7 +824,7 @@ module API { /** * Holds if `node` is considered "in scope" for this stage, meaning that we allow outgoing labelled edges - * to be materialised from here, and continue API graph construction from the successors 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 out of scope but can be tracked to a node in scope. * This predicate should thus not be used to block the tracking of use/def nodes, but only block the creation of new labelled edges. From 8e1dac09973be0906ca8dc2fa628efc122cb4011 Mon Sep 17 00:00:00 2001 From: Asger F Date: Wed, 17 Dec 2025 11:31:17 +0100 Subject: [PATCH 41/41] JS: Rename "in scope" to "in active file" --- .../ql/lib/semmle/javascript/ApiGraphs.qll | 23 ++++++++++--------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/javascript/ql/lib/semmle/javascript/ApiGraphs.qll b/javascript/ql/lib/semmle/javascript/ApiGraphs.qll index fde122ea78b4..e8717871ac27 100644 --- a/javascript/ql/lib/semmle/javascript/ApiGraphs.qll +++ b/javascript/ql/lib/semmle/javascript/ApiGraphs.qll @@ -823,14 +823,15 @@ module API { predicate isAdditionalDefRoot(Node node); /** - * Holds if `node` is considered "in scope" for this stage, meaning that we allow outgoing labelled edges + * 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 out of scope but can be tracked to a node in scope. + * 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 inScope(DataFlow::Node node); + predicate inActiveFile(DataFlow::Node node); } private module Stage { @@ -1025,11 +1026,11 @@ module API { * Holds if `rhs` is the right-hand side of a definition of node `nd`. */ predicate rhs(TApiNode nd, DataFlow::Node rhs) { - (S::inScope(rhs) or S::isAdditionalDefRoot(nd)) and + (S::inActiveFile(rhs) or S::isAdditionalDefRoot(nd)) and exists(string m | nd = MkModuleExport(m) | exports(m, rhs)) or rhs(_, _, rhs) and - S::inScope(rhs) and + S::inActiveFile(rhs) and nd = MkDef(rhs) or S::isAdditionalDefRoot(nd) and @@ -1084,7 +1085,7 @@ module API { exists(EntryPoint e | lbl = Label::entryPoint(e) and ref = e.getASource() and - S::inScope(ref) + S::inActiveFile(ref) ) or // property reads @@ -1286,7 +1287,7 @@ module API { * Holds if `ref` is a use of node `nd`. */ predicate use(TApiNode nd, DataFlow::Node ref) { - (S::inScope(ref) or S::isAdditionalUseRoot(nd)) and + (S::inActiveFile(ref) or S::isAdditionalUseRoot(nd)) and ( exists(string m, Module mod | nd = MkModuleDef(m) and mod = importableModule(m) | ref = DataFlow::moduleVarNode(mod) @@ -1313,7 +1314,7 @@ module API { ) or use(_, _, ref) and - S::inScope(ref) and + S::inActiveFile(ref) and nd = MkUse(ref) or S::isAdditionalUseRoot(nd) and @@ -1590,7 +1591,7 @@ module API { private predicate isOverlay() { databaseMetadata("isOverlay", "true") } bindingset[node] - predicate inScope(DataFlow::Node 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) @@ -1691,7 +1692,7 @@ module API { } bindingset[node] - predicate inScope(DataFlow::Node node) { isInOverlayChangedFile(node) } + predicate inActiveFile(DataFlow::Node node) { isInOverlayChangedFile(node) } } private module Stage2 = Stage; @@ -1754,7 +1755,7 @@ module API { predicate isAdditionalDefRoot(Node node) { none() } bindingset[node] - predicate inScope(DataFlow::Node node) { any() } + predicate inActiveFile(DataFlow::Node node) { any() } } private module Full = Stage;