JS: Deprecate getUnknownMember() and replace its uses with getArrayElement()

Although they mean slightly different things, every single call site
of getUnknownMember() just used it as a way to get array elements.

Since there is no known use-case for the original meaning of
getUnknownMember() I am deprecating it for now.
This commit is contained in:
Asger F
2025-03-14 13:14:50 +01:00
parent 4c1c0b79a6
commit ab74898bbb
9 changed files with 28 additions and 25 deletions

View File

@@ -10,6 +10,7 @@ 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 internal.CachedStages
/**
@@ -222,13 +223,17 @@ 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 getUnknownMember() {
Node getUnknownArrayElement() {
Stages::ApiStage::ref() and
result = this.getASuccessor(Label::unknownMember())
result = this.getASuccessor(Label::content(ContentPrivate::MkArrayElementUnknown()))
}
cached
@@ -274,7 +279,7 @@ module API {
Stages::ApiStage::ref() and
result = this.getMember(_)
or
result = this.getUnknownMember()
result = this.getUnknownArrayElement()
}
/**
@@ -1505,7 +1510,12 @@ module API {
/** Gets the `content` edge label for content `c`. */
LabelContent content(ContentPrivate::Content c) { result.getContent() = c }
/** Gets the `member` edge label for the unknown member. */
/**
* 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.
*/
LabelContent unknownMember() { result.getContent().isUnknownArrayElement() }
/**
@@ -1580,7 +1590,6 @@ module API {
/** Gets an entry-point label for the entry-point `e`. */
LabelEntryPoint entryPoint(API::EntryPoint e) { result.getEntryPoint() = e }
private import semmle.javascript.dataflow.internal.Contents::Private as ContentPrivate
private import LabelImpl
private module LabelImpl {
@@ -1676,7 +1685,7 @@ module API {
or
content instanceof ContentPrivate::MkPromiseError and result = "getPromisedError()"
or
content instanceof ContentPrivate::MkArrayElementUnknown and result = "getUnknownMember()"
content instanceof ContentPrivate::MkArrayElementUnknown and result = "getArrayElement()"
}
override string toString() {

View File

@@ -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()
}
}

View File

@@ -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()
}
@@ -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()
}
@@ -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

View File

@@ -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'})

View File

@@ -368,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

View File

@@ -297,13 +297,12 @@ module Stages {
exists(
API::moduleImport("foo")
.getMember("bar")
.getUnknownMember()
.getArrayElement()
.getAMember()
.getAParameter()
.getPromised()
.getReturn()
.getParameter(2)
.getUnknownMember()
.getInstance()
.getReceiver()
.getForwardingFunction()

View File

@@ -179,7 +179,6 @@ module ExternalApiUsedWithUntrustedData {
or
exists(string member |
node = base.getMember(member) and
not node = base.getUnknownMember() and
not isNumericString(member) and
not (member = "default" and base = API::moduleImport(_)) and
not member = "then" // use the 'promised' edges for .then callbacks
@@ -189,10 +188,7 @@ module ExternalApiUsedWithUntrustedData {
else result = basename + "['" + member.regexpReplaceAll("'", "\\'") + "']"
)
or
(
node = base.getUnknownMember() or
node = base.getMember(any(string s | isNumericString(s)))
) and
node = base.getArrayElement() and
result = basename + "[]"
or
// just collapse promises

View File

@@ -7,7 +7,7 @@ DataFlow::Node unverifiedDecode() {
verify
.getParameter(2)
.getMember("algorithms")
.getUnknownMember()
.getArrayElement()
.asSink()
.mayHaveStringValue("none") and
result = verify.getParameter(0).asSink()
@@ -32,7 +32,7 @@ DataFlow::Node verifiedDecode() {
not verify
.getParameter(2)
.getMember("algorithms")
.getUnknownMember()
.getArrayElement()
.asSink()
.mayHaveStringValue("none") or
not exists(verify.getParameter(2).getMember("algorithms"))

View File

@@ -72,7 +72,7 @@ module Execa {
override predicate isShellInterpreted(DataFlow::Node arg) {
// if shell: true then first and second args are sinks
// options can be third argument
arg = [this.getArgument(0), this.getParameter(1).getUnknownMember().asSink()] and
arg = [this.getArgument(0), this.getParameter(1).getArrayElement().asSink()] and
isExecaShellEnable(this.getParameter(2))
or
// options can be second argument