From 12c0ad0e042b70a591a8e947fc584c7409eaae4d Mon Sep 17 00:00:00 2001 From: Asger F Date: Fri, 10 Oct 2025 10:42:01 +0200 Subject: [PATCH 01/43] 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 7fc87536bfeb182365c76404661f585ca877925f Mon Sep 17 00:00:00 2001 From: Asger F Date: Thu, 9 Oct 2025 13:01:14 +0200 Subject: [PATCH 02/43] 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 8f413856e087d23269c63d9d930697823b8f114a Mon Sep 17 00:00:00 2001 From: Asger F Date: Thu, 9 Oct 2025 13:28:55 +0200 Subject: [PATCH 03/43] 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 fd65eeba0adb1fcc29517e6a839ae43397c9695b Mon Sep 17 00:00:00 2001 From: Asger F Date: Thu, 9 Oct 2025 13:46:53 +0200 Subject: [PATCH 04/43] 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 465f4b987d1cab02d3ef29b3ea9c53e0011c4a72 Mon Sep 17 00:00:00 2001 From: Asger F Date: Thu, 9 Oct 2025 13:55:38 +0200 Subject: [PATCH 05/43] 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 10db30a71593f4d824f665fdae6a7c800b1f8743 Mon Sep 17 00:00:00 2001 From: Asger F Date: Thu, 9 Oct 2025 14:07:09 +0200 Subject: [PATCH 06/43] 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 b69f8532d03339a85acb95e1d1f9f04c74cea838 Mon Sep 17 00:00:00 2001 From: Asger F Date: Thu, 9 Oct 2025 15:17:36 +0200 Subject: [PATCH 07/43] 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 c8108d109d4622daef72d3645b64dbd2a4d94a21 Mon Sep 17 00:00:00 2001 From: Asger F Date: Fri, 10 Oct 2025 10:01:25 +0200 Subject: [PATCH 08/43] 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 d63bafe7b6f3..10530bf84fcc 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 3fb76f76f70a..00929f19d279 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 3110e5a8ace74df165bb346d817c50262676ae4e Mon Sep 17 00:00:00 2001 From: Asger F Date: Fri, 10 Oct 2025 10:02:35 +0200 Subject: [PATCH 09/43] 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 a2a9518d2aad04229dc03f50f09a94c2c712c76b Mon Sep 17 00:00:00 2001 From: Asger F Date: Fri, 10 Oct 2025 10:03:03 +0200 Subject: [PATCH 10/43] 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 385bb32710c61019a2295db98a92434b14d5d35d Mon Sep 17 00:00:00 2001 From: Asger F Date: Fri, 10 Oct 2025 10:03:21 +0200 Subject: [PATCH 11/43] 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 3ed0f36bb1cfb8a428f200484cc28dcead82af2b Mon Sep 17 00:00:00 2001 From: Asger F Date: Fri, 10 Oct 2025 10:04:44 +0200 Subject: [PATCH 12/43] 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 6c00a7fb96353ed7ae2e20510d0841f84f6d354b Mon Sep 17 00:00:00 2001 From: Asger F Date: Fri, 10 Oct 2025 10:05:08 +0200 Subject: [PATCH 13/43] 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 295bc6981d6c3e62e4db0cbe382f5600af226113 Mon Sep 17 00:00:00 2001 From: Asger F Date: Fri, 10 Oct 2025 10:15:10 +0200 Subject: [PATCH 14/43] 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 123bc640919716b5383a50819e769c0d317e7c79 Mon Sep 17 00:00:00 2001 From: Asger F Date: Fri, 28 Nov 2025 10:12:52 +0100 Subject: [PATCH 15/43] 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 271567c88f64e9e054e973fd467bbd171e1959e6 Mon Sep 17 00:00:00 2001 From: Asger F Date: Fri, 28 Nov 2025 10:13:13 +0100 Subject: [PATCH 16/43] 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 daf04f1184f5b79ca1f8cf516aec13e44ec63635 Mon Sep 17 00:00:00 2001 From: Asger F Date: Fri, 28 Nov 2025 10:15:58 +0100 Subject: [PATCH 17/43] 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 b12d927020850145e970e9f142a48da062853d77 Mon Sep 17 00:00:00 2001 From: Asger F Date: Fri, 28 Nov 2025 10:16:20 +0100 Subject: [PATCH 18/43] 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 1001e86f201461689e15ed0616850e6a02d66286 Mon Sep 17 00:00:00 2001 From: Asger F Date: Fri, 28 Nov 2025 10:18:46 +0100 Subject: [PATCH 19/43] 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 9c37e076cc958a2b75c0b3380579b267119472f7 Mon Sep 17 00:00:00 2001 From: Asger F Date: Fri, 28 Nov 2025 10:19:06 +0100 Subject: [PATCH 20/43] 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 c9d3f06fbc3020235371f76649a51740a5fc5b21 Mon Sep 17 00:00:00 2001 From: Asger F Date: Fri, 10 Oct 2025 10:42:12 +0200 Subject: [PATCH 21/43] 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 4bd0f34938aeec8766223bb51e9135a682f77f00 Mon Sep 17 00:00:00 2001 From: Asger F Date: Fri, 10 Oct 2025 10:42:22 +0200 Subject: [PATCH 22/43] 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 825c08356f777037027eebe968c3f8b754c01b26 Mon Sep 17 00:00:00 2001 From: Asger F Date: Fri, 10 Oct 2025 11:18:27 +0200 Subject: [PATCH 23/43] 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 a6dfb8351c78e03546accb602195f426b69f7960 Mon Sep 17 00:00:00 2001 From: Asger F Date: Mon, 6 Oct 2025 11:39:54 +0200 Subject: [PATCH 24/43] 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 cf0e7652f4e819e9a9374cd4dc7bbe322a7452d2 Mon Sep 17 00:00:00 2001 From: Asger F Date: Mon, 3 Nov 2025 11:52:06 +0100 Subject: [PATCH 25/43] 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 c687dc93b0acee0a5e7185453a8ca0eabe1ac82f Mon Sep 17 00:00:00 2001 From: Asger F Date: Fri, 10 Oct 2025 21:37:01 +0200 Subject: [PATCH 26/43] 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 b6506ddd648d..10801d966542 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 ed3a8bdfa995a6b2f4f3f3c390206cf36091aef1 Mon Sep 17 00:00:00 2001 From: Asger F Date: Thu, 20 Nov 2025 16:04:18 +0100 Subject: [PATCH 27/43] 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 651608a170c61ff9af47f8f723c42993228b575f Mon Sep 17 00:00:00 2001 From: Asger F Date: Mon, 24 Nov 2025 11:46:38 +0100 Subject: [PATCH 28/43] 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 7974416e65c6cbd68413680daf2cf0f0d5cb81af Mon Sep 17 00:00:00 2001 From: Asger F Date: Mon, 24 Nov 2025 11:38:09 +0100 Subject: [PATCH 29/43] 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 962c128f20504e70fd74952c5fdc5a943f20ddfc Mon Sep 17 00:00:00 2001 From: Asger F Date: Mon, 24 Nov 2025 11:46:25 +0100 Subject: [PATCH 30/43] 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 8731eee10e0f5464d36d4c460869e1dc4ccb76c9 Mon Sep 17 00:00:00 2001 From: Asger F Date: Mon, 24 Nov 2025 13:33:03 +0100 Subject: [PATCH 31/43] 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 10530bf84fcc..5794beafd25d 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 27e8bcb347571aed4a717ef5275b07d647be1547 Mon Sep 17 00:00:00 2001 From: Asger F Date: Mon, 24 Nov 2025 14:12:03 +0100 Subject: [PATCH 32/43] 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 97d369cf4e20f9cff7ee6cfb92ba8ea33f7efb95 Mon Sep 17 00:00:00 2001 From: Asger F Date: Mon, 24 Nov 2025 14:35:53 +0100 Subject: [PATCH 33/43] 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 869efb8a484cb507406b568ea04bddb7e754cf02 Mon Sep 17 00:00:00 2001 From: Asger F Date: Mon, 24 Nov 2025 14:37:51 +0100 Subject: [PATCH 34/43] 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 369848a8706512af63f39fd30aabafb2b9a960ac Mon Sep 17 00:00:00 2001 From: Asger F Date: Tue, 25 Nov 2025 11:52:44 +0100 Subject: [PATCH 35/43] 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 cae27c40be5878bf5279ab5e1c00ac5d5c1a3aca Mon Sep 17 00:00:00 2001 From: Asger F Date: Tue, 25 Nov 2025 14:09:13 +0100 Subject: [PATCH 36/43] 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 9721b4e0f597d0be11a5b56872b628b35324ba28 Mon Sep 17 00:00:00 2001 From: Asger F Date: Tue, 2 Dec 2025 14:42:51 +0100 Subject: [PATCH 37/43] 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 3710942e9e47..d55f9843e1b9 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 d0dbc91aa96c166c0b725017ccca05be033b1d16 Mon Sep 17 00:00:00 2001 From: Asger F Date: Tue, 9 Dec 2025 09:02:55 +0100 Subject: [PATCH 38/43] 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 56a6fe4c085cb903299806dcaaadadb1e2e877a5 Mon Sep 17 00:00:00 2001 From: Asger F Date: Tue, 9 Dec 2025 09:03:11 +0100 Subject: [PATCH 39/43] 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 87049bd07e24236acea7d13cee654c1d2ad4fc2b Mon Sep 17 00:00:00 2001 From: Asger F Date: Wed, 17 Dec 2025 10:59:37 +0100 Subject: [PATCH 40/43] 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 e16cacd48d618a153e1962d0b4f7007f9afc86a0 Mon Sep 17 00:00:00 2001 From: Asger F Date: Wed, 17 Dec 2025 11:31:17 +0100 Subject: [PATCH 41/43] 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; From cf0b77074f1436ab04ef0a66845167e1721d192c Mon Sep 17 00:00:00 2001 From: Asger F Date: Mon, 5 Jan 2026 11:22:23 +0100 Subject: [PATCH 42/43] JS: Workaround forceLocal not supporting 'result' column A bug made it into the release which causes compilation errors when forceLocal is used on a predicate with a result column. This commit works around the issue by converting the result column to a positional parameter, for the predicates that we use forceLocal on. It should be safe to revert this commit once the compiler fix has made it into a stable release. --- .../ql/lib/semmle/javascript/ApiGraphs.qll | 141 ++++++++++-------- 1 file changed, 78 insertions(+), 63 deletions(-) diff --git a/javascript/ql/lib/semmle/javascript/ApiGraphs.qll b/javascript/ql/lib/semmle/javascript/ApiGraphs.qll index e8717871ac27..3fcb2840fa08 100644 --- a/javascript/ql/lib/semmle/javascript/ApiGraphs.qll +++ b/javascript/ql/lib/semmle/javascript/ApiGraphs.qll @@ -132,7 +132,7 @@ module API { */ pragma[inline] DataFlow::Node getAValueReachableFromSource() { - Impl::trackUseNode(this.asSource()).flowsTo(result) + Impl::trackUseNode(this.asSource(), result.getALocalSource()) } /** @@ -171,7 +171,7 @@ module API { CallNode getMaybePromisifiedCall() { result = this.getACall() or - result = Impl::getAPromisifiedInvocation(this, _, _) + Impl::getAPromisifiedInvocation(this, _, _, result) } /** @@ -210,7 +210,7 @@ module API { * This is similar to `asSink()` but additionally includes nodes that transitively reach a sink by data flow. * See `asSink()` for examples. */ - DataFlow::Node getAValueReachingSink() { result = Impl::trackDefNode(this.asSink()) } + DataFlow::Node getAValueReachingSink() { Impl::trackDefNode(this.asSink(), result) } /** * Gets a node representing member `m` of this API component. @@ -855,7 +855,7 @@ module API { ) or exists(DataFlow::Node def, DataFlow::SourceNode pred | - rhs(base, def) and pred = trackDefNode(def) + rhs(base, def) and trackDefNode(def, pred) | // from `x` to a definition of `x.prop` exists(DataFlow::PropWrite pw | pw = pred.getAPropertyWrite() | @@ -956,8 +956,11 @@ module API { lbl = Label::spreadArgument(i) ) or - exists(DataFlow::SourceNode src, DataFlow::PropWrite pw | - use(base, src) and pw = trackUseNode(src).getAPropertyWrite() and rhs = pw.getRhs() + exists(DataFlow::SourceNode src, DataFlow::SourceNode mid, DataFlow::PropWrite pw | + use(base, src) and + trackUseNode(src, mid) and + pw = mid.getAPropertyWrite() and + rhs = pw.getRhs() | lbl = Label::memberFromRef(pw) ) @@ -1099,7 +1102,7 @@ module API { ) or exists(DataFlow::SourceNode src, DataFlow::SourceNode pred | - use(base, src) and pred = trackUseNode(src) + use(base, src) and trackUseNode(src, pred) | lbl = Label::instance() and ref = pred.getAnInstantiation() @@ -1133,7 +1136,7 @@ module API { ) or exists(DataFlow::Node def, DataFlow::FunctionNode fn | - rhs(base, def) and fn = trackDefNode(def) + rhs(base, def) and trackDefNode(def, fn) | exists(int i | lbl = Label::parameter(i) and @@ -1145,7 +1148,7 @@ module API { ) or exists(DataFlow::Node def, DataFlow::ClassNode cls, int i | - rhs(base, def) and cls = trackDefNode(def) + rhs(base, def) and trackDefNode(def, cls) | lbl = Label::parameter(i) and ref = cls.getConstructor().getParameter(i) @@ -1188,7 +1191,7 @@ module API { private predicate useNodeFlowsToDecorator(TApiNode base, Decorator decorator) { exists(DataFlow::SourceNode decoratorSrc | use(base, decoratorSrc) and - trackUseNode(decoratorSrc).flowsToExpr(decorator.getExpression()) + trackUseNode(decoratorSrc, decorator.getExpression().flow().getALocalSource()) ) } @@ -1271,9 +1274,9 @@ module API { private predicate needsDefNode(DataFlow::ClassNode cls) { hasSemantics(cls) and ( - cls = trackDefNode(_) + trackDefNode(_, cls) or - cls.getAnInstanceReference() = trackDefNode(_) + trackDefNode(_, cls.getAnInstanceReference()) or needsDefNode(cls.getADirectSubClass()) or @@ -1296,8 +1299,8 @@ module API { exists(string m, Module mod | nd = MkModuleExport(m) and mod = importableModule(m) | ref = DataFlow::exportsVarNode(mod) or - exists(DataFlow::Node base | use(MkModuleDef(m), base) | - ref = trackUseNode(base).getAPropertyRead("exports") + exists(DataFlow::Node base, DataFlow::SourceNode mid | use(MkModuleDef(m), base) | + trackUseNode(base, mid) and ref = mid.getAPropertyRead("exports") ) ) or @@ -1415,34 +1418,36 @@ module API { } /** - * Gets a node that is inter-procedurally reachable from `nd`, which is a use of some node. + * Holds if `target` is inter-procedurally reachable from `nd`, which is a use of some node. */ - DataFlow::SourceNode trackUseNode(DataFlow::SourceNode nd) { - result = trackUseNode(nd, false, 0, "") + predicate trackUseNode(DataFlow::SourceNode nd, DataFlow::SourceNode target) { + target = 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, _, _, _, _) + predicate trackUseNodeAnyState(DataFlow::SourceNode nd, DataFlow::SourceNode target) { + target = trackUseNode(nd, _, _, _, _) } - private DataFlow::SourceNode trackDefNode(DataFlow::Node nd, DataFlow::TypeBackTracker t) { + private predicate trackDefNode( + DataFlow::Node nd, DataFlow::TypeBackTracker t, DataFlow::SourceNode target + ) { t.start() and rhs(_, nd) and - result = nd.getALocalSource() + target = 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()) + exists(Import imp | trackDefNode(nd, t.continue(), imp.getImportedModuleNodeStrict()) | + target = DataFlow::exportsVarNode(imp.getImportedModule()) or - result = DataFlow::moduleVarNode(imp.getImportedModule()).getAPropertyRead("exports") + target = DataFlow::moduleVarNode(imp.getImportedModule()).getAPropertyRead("exports") ) or exists(ObjectExpr obj | - obj = trackDefNode(nd, t.continue()).asExpr() and - result = + trackDefNode(nd, t.continue(), obj.flow()) and + target = obj.getAProperty() .(SpreadProperty) .getInit() @@ -1452,7 +1457,7 @@ module API { .getALocalSource() ) or - t = defStep(nd, result) + t = defStep(nd, target) } /** @@ -1465,7 +1470,7 @@ module API { 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 + trackDefNode(nd, t, next) and StepSummary::step(prev, next, summary) and result = t.prepend(summary) and // Block argument-passing into 'this' when it determines the call target @@ -1474,16 +1479,18 @@ module API { } /** - * Gets a node that inter-procedurally flows into `nd`, which is a definition of some node. + * Holds if `target` inter-procedurally flows into `nd`, which is a definition of some node. */ - DataFlow::SourceNode trackDefNode(DataFlow::Node nd) { - result = trackDefNode(nd, DataFlow::TypeBackTracker::end()) + predicate trackDefNode(DataFlow::Node nd, DataFlow::SourceNode target) { + trackDefNode(nd, DataFlow::TypeBackTracker::end(), target) } /** * 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, _) } + predicate trackDefNodeAnyState(DataFlow::Node nd, DataFlow::SourceNode target) { + trackDefNode(nd, _, target) + } private DataFlow::SourceNode awaited(DataFlow::InvokeNode call, DataFlow::TypeTracker t) { t.startInPromise() and @@ -1539,10 +1546,11 @@ module API { ) ) or - exists(DataFlow::Node def | + exists(DataFlow::Node def, DataFlow::Node mid | rhs(pred, def) and lbl = Label::instance() and - succ = MkClassInstance(trackDefNode(def)) + trackDefNode(def, mid) and + succ = MkClassInstance(mid) ) or exists(string moduleName, string exportName | @@ -1554,14 +1562,14 @@ module API { exists(DataFlow::Node nd, DataFlow::FunctionNode f | f.getFunction().isAsync() and pred = MkDef(nd) and - f = trackDefNode(nd) and + trackDefNode(nd, f) 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) + getAPromisifiedInvocation(pred, bound, succ, call) ) } @@ -1569,11 +1577,13 @@ module API { * Gets a call to a promisified function represented by `callee` where * `bound` arguments have been bound. */ - DataFlow::InvokeNode getAPromisifiedInvocation(TApiNode callee, int bound, TApiNode succ) { + predicate getAPromisifiedInvocation( + TApiNode callee, int bound, TApiNode succ, DataFlow::InvokeNode invoke + ) { exists(DataFlow::SourceNode src | use(callee, src) and - trackUseNode(src, true, bound, "").flowsTo(result.getCalleeNode()) and - succ = Impl::MkSyntheticCallbackArg(result) + trackUseNode(src, true, bound, "").flowsTo(invoke.getCalleeNode()) and + succ = Impl::MkSyntheticCallbackArg(invoke) ) } } @@ -1606,23 +1616,24 @@ module API { 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) + predicate trackUseNode(DataFlow::SourceNode nd, DataFlow::SourceNode target) = + forceLocal(Stage1::trackUseNode/2)(nd, target) - DataFlow::SourceNode trackUseNodeAnyState(DataFlow::SourceNode nd) = - forceLocal(Stage1::trackUseNodeAnyState/1)(nd, result) + predicate trackUseNodeAnyState(DataFlow::SourceNode nd, DataFlow::SourceNode target) = + forceLocal(Stage1::trackUseNodeAnyState/2)(nd, target) - DataFlow::SourceNode trackDefNode(DataFlow::Node nd) = - forceLocal(Stage1::trackDefNode/1)(nd, result) + predicate trackDefNode(DataFlow::Node nd, DataFlow::SourceNode target) = + forceLocal(Stage1::trackDefNode/2)(nd, target) - DataFlow::SourceNode trackDefNodeAnyState(DataFlow::Node nd) = - forceLocal(Stage1::trackDefNodeAnyState/1)(nd, result) + predicate trackDefNodeAnyState(DataFlow::Node nd, DataFlow::SourceNode target) = + forceLocal(Stage1::trackDefNodeAnyState/2)(nd, target) 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) + predicate getAPromisifiedInvocation( + TApiNode callee, int bound, TApiNode succ, DataFlow::InvokeNode invoke + ) = forceLocal(Stage1::getAPromisifiedInvocation/4)(callee, bound, succ, invoke) } private module Stage2Input implements StageInputSig { @@ -1651,8 +1662,9 @@ module API { /** 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) + exists(DataFlow::Node mid | + Stage1Local::trackUseNodeAnyState(nd, mid) and + stepIntoOverlay(mid, _) ) } @@ -1677,8 +1689,9 @@ module API { /** 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)) + exists(DataFlow::Node mid | + Stage1Local::trackDefNodeAnyState(nd, mid) and + stepOutOfOverlay(_, mid) ) } @@ -1714,17 +1727,17 @@ module API { } cached - DataFlow::SourceNode trackUseNode(DataFlow::SourceNode nd) { - result = Stage1Local::trackUseNode(nd) + predicate trackUseNode(DataFlow::SourceNode nd, DataFlow::SourceNode target) { + Stage1Local::trackUseNode(nd, target) or - result = Stage2::trackUseNode(nd) + Stage2::trackUseNode(nd, target) } cached - DataFlow::SourceNode trackDefNode(DataFlow::Node nd) { - result = Stage1Local::trackDefNode(nd) + predicate trackDefNode(DataFlow::Node nd, DataFlow::SourceNode target) { + Stage1Local::trackDefNode(nd, target) or - result = Stage2::trackDefNode(nd) + Stage2::trackDefNode(nd, target) } cached @@ -1735,10 +1748,12 @@ module API { } cached - DataFlow::InvokeNode getAPromisifiedInvocation(TApiNode callee, int bound, TApiNode succ) { - result = Stage1Local::getAPromisifiedInvocation(callee, bound, succ) + predicate getAPromisifiedInvocation( + TApiNode callee, int bound, TApiNode succ, DataFlow::InvokeNode invoke + ) { + Stage1Local::getAPromisifiedInvocation(callee, bound, succ, invoke) or - result = Stage2::getAPromisifiedInvocation(callee, bound, succ) + Stage2::getAPromisifiedInvocation(callee, bound, succ, invoke) } } @@ -1808,7 +1823,7 @@ module API { InvokeNode() { this = callee.getReturn().asSource() or this = callee.getInstance().asSource() or - this = Impl::getAPromisifiedInvocation(callee, _, _) + Impl::getAPromisifiedInvocation(callee, _, _, this) } /** Gets the API node for the `i`th parameter of this invocation. */ From da9aafc3b0f3033687fad22a035c0f6fdd73bfc7 Mon Sep 17 00:00:00 2001 From: Asger F Date: Tue, 13 Jan 2026 10:53:59 +0100 Subject: [PATCH 43/43] JS: Also track additional use-steps crossing the overlay boundary --- javascript/ql/lib/semmle/javascript/ApiGraphs.qll | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/javascript/ql/lib/semmle/javascript/ApiGraphs.qll b/javascript/ql/lib/semmle/javascript/ApiGraphs.qll index 3fcb2840fa08..c891804eaa71 100644 --- a/javascript/ql/lib/semmle/javascript/ApiGraphs.qll +++ b/javascript/ql/lib/semmle/javascript/ApiGraphs.qll @@ -1650,10 +1650,17 @@ module API { isInOverlayChangedFile(node) } + pragma[nomagic] + private predicate step(DataFlow::SourceNode node1, DataFlow::SourceNode node2) { + StepSummary::step(node1, node2, _) + or + AdditionalUseStep::step(node1, node2) + } + /** 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 + step(node1, node2) and isInOverlayChangedFile(node2) and not isInOverlayChangedFileLate(node1) and hasSemanticsLate(node1) @@ -1680,7 +1687,7 @@ module API { /** 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 + step(node1, node2) and isInOverlayChangedFile(node1) and not isInOverlayChangedFileLate(node2) and hasSemanticsLate(node2)