Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions javascript/ql/lib/ext/tanstack.model.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,6 @@ extensions:
- ["@tanstack/angular-query-experimental", "Member[injectQuery]", "Argument[0].ReturnValue.Member[queryFn].ReturnValue", "ReturnValue.Member[data].Awaited", "value"]
- ["@tanstack/angular-query", "Member[injectQuery]", "Argument[0].ReturnValue.Member[queryFn].ReturnValue", "ReturnValue.Member[data].Awaited", "value"]
- ["@tanstack/vue-query", "Member[useQuery]", "Argument[0].Member[queryFn].ReturnValue.Awaited", "ReturnValue.Member[data]", "value"]
- ["@tanstack/vue-query", "Member[useQueries]", "Argument[0].Member[queries].ArrayElement.Member[queryFn].ReturnValue.Awaited", "ReturnValue.AnyMember.Member[data]", "value"]
- ["@tanstack/react-query", "Member[useQueries]", "Argument[0].Member[queries].ArrayElement.Member[queryFn].ReturnValue.Awaited", "ReturnValue.AnyMember.Member[data]", "value"]
- ["@tanstack/vue-query", "Member[useQueries]", "Argument[0].Member[queries].ArrayElement.Member[queryFn].ReturnValue.Awaited", "ReturnValue.ArrayElement.Member[data]", "value"]
- ["@tanstack/react-query", "Member[useQueries]", "Argument[0].Member[queries].ArrayElement.Member[queryFn].ReturnValue.Awaited", "ReturnValue.ArrayElement.Member[data]", "value"]
- ["@tanstack/react-query", "Member[useQuery]", "Argument[0].Member[queryFn].ReturnValue.Awaited", "ReturnValue.Member[data]", "value"]
158 changes: 124 additions & 34 deletions javascript/ql/lib/semmle/javascript/ApiGraphs.qll
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@
import javascript
private import semmle.javascript.dataflow.internal.FlowSteps as FlowSteps
private import semmle.javascript.dataflow.internal.PreCallGraphStep
private import semmle.javascript.dataflow.internal.StepSummary
private import semmle.javascript.dataflow.internal.sharedlib.SummaryTypeTracker as SummaryTypeTracker
private import semmle.javascript.dataflow.internal.Contents::Private as ContentPrivate
private import semmle.javascript.DynamicPropertyAccess
private import internal.CachedStages

/**
Expand Down Expand Up @@ -220,15 +224,53 @@ module API {
}

/**
* Gets a node representing a member of this API component where the name of the member is
* not known statically.
* DEPRECATED. Use either `getArrayElement()` or `getAMember()` instead.
*/
deprecated Node getUnknownMember() { result = this.getArrayElement() }

/**
* Gets an array element of unknown index.
*/
cached
Node getUnknownArrayElement() {
Stages::ApiStage::ref() and
result = this.getASuccessor(Label::content(ContentPrivate::MkArrayElementUnknown()))
}

cached
Node getUnknownMember() {
private Node getContentRaw(DataFlow::Content content) {
Stages::ApiStage::ref() and
result = this.getASuccessor(Label::unknownMember())
result = this.getASuccessor(Label::content(content))
}

/**
* Gets a representative for the `content` of this value.
*
* When possible, it is preferrable to use one of the specialized variants of this predicate, such as `getMember`.
*/
pragma[inline]
Node getContent(DataFlow::Content content) {
result = this.getContentRaw(content)
or
result = this.getMember(content.asPropertyName())
}

/**
* Gets a representative for the `contents` of this value.
*/
bindingset[contents]
pragma[inline_late]
private Node getContents(DataFlow::ContentSet contents) {
// We always use getAStoreContent when generating content edges, and we always use getAReadContent when querying the graph.
result = this.getContent(contents.getAReadContent())
}

/**
* Gets a node representing an arbitrary array element in the array represented by this node.
*/
cached
Node getArrayElement() { result = this.getContents(DataFlow::ContentSet::arrayElement()) }

/**
* Gets a node representing a member of this API component where the name of the member may
* or may not be known statically.
Expand All @@ -238,7 +280,7 @@ module API {
Stages::ApiStage::ref() and
result = this.getMember(_)
or
result = this.getUnknownMember()
result = this.getUnknownArrayElement()
}

/**
Expand Down Expand Up @@ -790,6 +832,11 @@ module API {
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()
Expand Down Expand Up @@ -982,6 +1029,11 @@ module API {
// 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 |
Expand Down Expand Up @@ -1199,8 +1251,6 @@ module API {
t = useStep(nd, promisified, boundArgs, prop, result)
}

private import semmle.javascript.dataflow.internal.StepSummary

/**
* 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
Expand Down Expand Up @@ -1458,8 +1508,21 @@ module API {
bindingset[result]
LabelMember member(string m) { result.getProperty() = m }

/** Gets the `member` edge label for the unknown member. */
LabelUnknownMember unknownMember() { any() }
/** Gets the `content` edge label for content `c`. */
LabelContent content(ContentPrivate::Content c) { result.getContent() = c }

/**
* Gets the edge label for an unknown member.
*
* Currently this is represented the same way as an unknown array element, but this may
* change in the future.
*/
ApiLabel unknownMember() { result = arrayElement() }

/**
* Gets the edge label for an unknown array element.
*/
LabelContent arrayElement() { result.getContent().isUnknownArrayElement() }

/**
* Gets a property name referred to by the given dynamic property access,
Expand All @@ -1482,6 +1545,11 @@ module API {
result = unique(string s | s = getAnIndirectPropName(ref))
}

pragma[nomagic]
private predicate isEnumeratedPropName(DataFlow::Node node) {
node.getAPredecessor*() instanceof EnumeratedPropName
}

/** Gets the `member` edge label for the given property reference. */
ApiLabel memberFromRef(DataFlow::PropRef pr) {
exists(string pn | pn = pr.getPropertyName() or pn = getIndirectPropName(pr) |
Expand All @@ -1493,7 +1561,9 @@ module API {
or
not exists(pr.getPropertyName()) and
not exists(getIndirectPropName(pr)) and
result = unknownMember()
// Avoid assignments in an extend-like pattern
not isEnumeratedPropName(pr.getPropertyNameExpr().flow()) and
result = arrayElement()
}

/** Gets the `instance` edge label. */
Expand All @@ -1516,10 +1586,10 @@ module API {
LabelForwardingFunction forwardingFunction() { any() }

/** Gets the `promised` edge label connecting a promise to its contained value. */
LabelPromised promised() { any() }
LabelContent promised() { result.getContent() = ContentPrivate::MkPromiseValue() }

/** Gets the `promisedError` edge label connecting a promise to its rejected value. */
LabelPromisedError promisedError() { any() }
LabelContent promisedError() { result.getContent() = ContentPrivate::MkPromiseError() }

/** Gets the label for an edge leading from a value `D` to any class that has `D` as a decorator. */
LabelDecoratedClass decoratedClass() { any() }
Expand All @@ -1542,18 +1612,12 @@ module API {
exists(Impl::MkModuleImport(mod))
} or
MkLabelInstance() or
MkLabelMember(string prop) {
exports(_, prop, _) or
exists(any(DataFlow::ClassNode c).getInstanceMethod(prop)) or
prop = "exports" or
prop = any(CanonicalName c).getName() or
prop = any(DataFlow::PropRef p).getPropertyName() or
exists(Impl::MkTypeUse(_, prop)) or
exists(any(Module m).getAnExportedValue(prop)) or
PreCallGraphStep::loadStep(_, _, prop) or
PreCallGraphStep::storeStep(_, _, prop)
MkLabelContent(DataFlow::Content content) or
MkLabelMember(string name) {
name instanceof PropertyName
or
exists(Impl::MkTypeUse(_, name))
} or
MkLabelUnknownMember() or
MkLabelParameter(int i) {
i =
[0 .. max(int args |
Expand All @@ -1564,8 +1628,6 @@ module API {
} or
MkLabelReceiver() or
MkLabelReturn() or
MkLabelPromised() or
MkLabelPromisedError() or
MkLabelDecoratedClass() or
MkLabelDecoratedMember() or
MkLabelDecoratedParameter() or
Expand All @@ -1585,13 +1647,13 @@ module API {
}

/** A label that gets a promised value. */
class LabelPromised extends ApiLabel, MkLabelPromised {
override string toString() { result = "getPromised()" }
deprecated class LabelPromised extends ApiLabel {
LabelPromised() { this = MkLabelContent(ContentPrivate::MkPromiseValue()) }
}

/** A label that gets a rejected promise. */
class LabelPromisedError extends ApiLabel, MkLabelPromisedError {
override string toString() { result = "getPromisedError()" }
deprecated class LabelPromisedError extends ApiLabel {
LabelPromisedError() { this = MkLabelContent(ContentPrivate::MkPromiseError()) }
}

/** A label that gets the return value of a function. */
Expand All @@ -1617,9 +1679,39 @@ module API {
override string toString() { result = "getInstance()" }
}

/** A label for a content. */
class LabelContent extends ApiLabel, MkLabelContent {
private DataFlow::Content content;

LabelContent() {
this = MkLabelContent(content) and
// Property names are represented by LabelMember to ensure additional property
// names from PreCallGraph step are included, as well as those from MkTypeUse.
not content instanceof ContentPrivate::MkPropertyContent
}

/** Gets the content associated with this label. */
DataFlow::Content getContent() { result = content }

private string specialisedToString() {
content instanceof ContentPrivate::MkPromiseValue and result = "getPromised()"
or
content instanceof ContentPrivate::MkPromiseError and result = "getPromisedError()"
or
content instanceof ContentPrivate::MkArrayElementUnknown and result = "getArrayElement()"
}

override string toString() {
result = this.specialisedToString()
or
not exists(this.specialisedToString()) and
result = "getContent(" + content + ")"
}
}

/** A label for the member named `prop`. */
class LabelMember extends ApiLabel, MkLabelMember {
string prop;
private string prop;

LabelMember() { this = MkLabelMember(prop) }

Expand All @@ -1630,10 +1722,8 @@ module API {
}

/** A label for a member with an unknown name. */
class LabelUnknownMember extends ApiLabel, MkLabelUnknownMember {
LabelUnknownMember() { this = MkLabelUnknownMember() }

override string toString() { result = "getUnknownMember()" }
deprecated class LabelUnknownMember extends LabelContent {
LabelUnknownMember() { this.getContent().isUnknownArrayElement() }
}

/** A label for parameter `i`. */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,16 @@ module Private {
this = getAPreciseArrayIndex().toString()
or
isAccessPathTokenPresent("Member", this)
or
this = any(ImportSpecifier spec).getImportedName()
or
this = any(ExportSpecifier n).getExportedName()
or
this = any(ExportNamedDeclaration d).getAnExportedDecl().getName()
or
this = any(MemberDefinition m).getName()
or
this = ["exports", "default"]
}

/** Gets the array index corresponding to this property name. */
Expand Down
2 changes: 1 addition & 1 deletion javascript/ql/lib/semmle/javascript/frameworks/D3.qll
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ module D3 {
or
this = d3Selection().getMember("node").getReturn().asSource()
or
this = d3Selection().getMember("nodes").getReturn().getUnknownMember().asSource()
this = d3Selection().getMember("nodes").getReturn().getArrayElement().asSource()
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ module Puppeteer {
or
result = [browser(), context()].getMember("newPage").getReturn().getPromised()
or
result = [browser(), context()].getMember("pages").getReturn().getPromised().getUnknownMember()
result = [browser(), context()].getMember("pages").getReturn().getPromised().getArrayElement()
or
result = target().getMember("page").getReturn().getPromised()
}
Expand All @@ -45,7 +45,7 @@ module Puppeteer {
or
result = [page(), browser()].getMember("target").getReturn()
or
result = context().getMember("targets").getReturn().getUnknownMember()
result = context().getMember("targets").getReturn().getArrayElement()
or
result = target().getMember("opener").getReturn()
}
Expand All @@ -58,7 +58,7 @@ module Puppeteer {
or
result = [page(), target()].getMember("browserContext").getReturn()
or
result = browser().getMember("browserContexts").getReturn().getUnknownMember()
result = browser().getMember("browserContexts").getReturn().getArrayElement()
or
result = browser().getMember("createIncognitoBrowserContext").getReturn().getPromised()
or
Expand Down
3 changes: 1 addition & 2 deletions javascript/ql/lib/semmle/javascript/frameworks/Vuex.qll
Original file line number Diff line number Diff line change
Expand Up @@ -104,8 +104,7 @@ module Vuex {
storeName = this.getNamespace() + localName
or
// mapGetters(['foo', 'bar'])
this.getLastParameter().getUnknownMember().getAValueReachingSink().getStringValue() =
localName and
this.getLastParameter().getArrayElement().getAValueReachingSink().getStringValue() = localName and
storeName = this.getNamespace() + localName
or
// mapGetters({foo: 'bar'})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -162,8 +162,8 @@ API::Node getExtraSuccessorFromNode(API::Node node, AccessPathTokenBase token) {
token.getName() = "Awaited" and
result = node.getPromised()
or
token.getName() = "ArrayElement" and
result = node.getMember(DataFlow::PseudoProperties::arrayElement())
token.getName() = ["ArrayElement", "Element"] and
result = node.getArrayElement()
or
token.getName() = "Element" and
result = node.getMember(DataFlow::PseudoProperties::arrayLikeElement())
Expand All @@ -172,11 +172,6 @@ API::Node getExtraSuccessorFromNode(API::Node node, AccessPathTokenBase token) {
token.getName() = "MapValue" and
result = node.getMember(DataFlow::PseudoProperties::mapValueAll())
or
// Currently we need to include the "unknown member" for ArrayElement and Element since
// API graphs do not use store/load steps for arrays
token.getName() = ["ArrayElement", "Element"] and
result = node.getUnknownMember()
or
token.getName() = "Parameter" and
token.getAnArgument() = "this" and
result = node.getReceiver()
Expand Down Expand Up @@ -373,7 +368,7 @@ bindingset[pred]
predicate apiGraphHasEdge(API::Node pred, string path, API::Node succ) {
exists(string name | succ = pred.getMember(name) and path = "Member[" + name + "]")
or
succ = pred.getUnknownMember() and path = "AnyMember"
succ = pred.getUnknownArrayElement() and path = "ArrayElement"
or
succ = pred.getInstance() and path = "Instance"
or
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -297,13 +297,12 @@ module Stages {
exists(
API::moduleImport("foo")
.getMember("bar")
.getUnknownMember()
.getArrayElement()
.getAMember()
.getAParameter()
.getPromised()
.getReturn()
.getParameter(2)
.getUnknownMember()
.getInstance()
.getReceiver()
.getForwardingFunction()
Expand Down
Loading