Skip to content

Commit 1324c11

Browse files
authored
Merge pull request #19012 from asgerf/js/api-graph-array-element
JS: Make API graphs use steps from summaries
2 parents 80d8018 + 53ba588 commit 1324c11

File tree

18 files changed

+181
-64
lines changed

18 files changed

+181
-64
lines changed

javascript/ql/lib/ext/tanstack.model.yml

+2-2
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,6 @@ extensions:
66
- ["@tanstack/angular-query-experimental", "Member[injectQuery]", "Argument[0].ReturnValue.Member[queryFn].ReturnValue", "ReturnValue.Member[data].Awaited", "value"]
77
- ["@tanstack/angular-query", "Member[injectQuery]", "Argument[0].ReturnValue.Member[queryFn].ReturnValue", "ReturnValue.Member[data].Awaited", "value"]
88
- ["@tanstack/vue-query", "Member[useQuery]", "Argument[0].Member[queryFn].ReturnValue.Awaited", "ReturnValue.Member[data]", "value"]
9-
- ["@tanstack/vue-query", "Member[useQueries]", "Argument[0].Member[queries].ArrayElement.Member[queryFn].ReturnValue.Awaited", "ReturnValue.AnyMember.Member[data]", "value"]
10-
- ["@tanstack/react-query", "Member[useQueries]", "Argument[0].Member[queries].ArrayElement.Member[queryFn].ReturnValue.Awaited", "ReturnValue.AnyMember.Member[data]", "value"]
9+
- ["@tanstack/vue-query", "Member[useQueries]", "Argument[0].Member[queries].ArrayElement.Member[queryFn].ReturnValue.Awaited", "ReturnValue.ArrayElement.Member[data]", "value"]
10+
- ["@tanstack/react-query", "Member[useQueries]", "Argument[0].Member[queries].ArrayElement.Member[queryFn].ReturnValue.Awaited", "ReturnValue.ArrayElement.Member[data]", "value"]
1111
- ["@tanstack/react-query", "Member[useQuery]", "Argument[0].Member[queryFn].ReturnValue.Awaited", "ReturnValue.Member[data]", "value"]

javascript/ql/lib/semmle/javascript/ApiGraphs.qll

+124-34
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,10 @@
88
import javascript
99
private import semmle.javascript.dataflow.internal.FlowSteps as FlowSteps
1010
private import semmle.javascript.dataflow.internal.PreCallGraphStep
11+
private import semmle.javascript.dataflow.internal.StepSummary
12+
private import semmle.javascript.dataflow.internal.sharedlib.SummaryTypeTracker as SummaryTypeTracker
13+
private import semmle.javascript.dataflow.internal.Contents::Private as ContentPrivate
14+
private import semmle.javascript.DynamicPropertyAccess
1115
private import internal.CachedStages
1216

1317
/**
@@ -220,15 +224,53 @@ module API {
220224
}
221225

222226
/**
223-
* Gets a node representing a member of this API component where the name of the member is
224-
* not known statically.
227+
* DEPRECATED. Use either `getArrayElement()` or `getAMember()` instead.
225228
*/
229+
deprecated Node getUnknownMember() { result = this.getArrayElement() }
230+
231+
/**
232+
* Gets an array element of unknown index.
233+
*/
234+
cached
235+
Node getUnknownArrayElement() {
236+
Stages::ApiStage::ref() and
237+
result = this.getASuccessor(Label::content(ContentPrivate::MkArrayElementUnknown()))
238+
}
239+
226240
cached
227-
Node getUnknownMember() {
241+
private Node getContentRaw(DataFlow::Content content) {
228242
Stages::ApiStage::ref() and
229-
result = this.getASuccessor(Label::unknownMember())
243+
result = this.getASuccessor(Label::content(content))
230244
}
231245

246+
/**
247+
* Gets a representative for the `content` of this value.
248+
*
249+
* When possible, it is preferrable to use one of the specialized variants of this predicate, such as `getMember`.
250+
*/
251+
pragma[inline]
252+
Node getContent(DataFlow::Content content) {
253+
result = this.getContentRaw(content)
254+
or
255+
result = this.getMember(content.asPropertyName())
256+
}
257+
258+
/**
259+
* Gets a representative for the `contents` of this value.
260+
*/
261+
bindingset[contents]
262+
pragma[inline_late]
263+
private Node getContents(DataFlow::ContentSet contents) {
264+
// We always use getAStoreContent when generating content edges, and we always use getAReadContent when querying the graph.
265+
result = this.getContent(contents.getAReadContent())
266+
}
267+
268+
/**
269+
* Gets a node representing an arbitrary array element in the array represented by this node.
270+
*/
271+
cached
272+
Node getArrayElement() { result = this.getContents(DataFlow::ContentSet::arrayElement()) }
273+
232274
/**
233275
* Gets a node representing a member of this API component where the name of the member may
234276
* or may not be known statically.
@@ -238,7 +280,7 @@ module API {
238280
Stages::ApiStage::ref() and
239281
result = this.getMember(_)
240282
or
241-
result = this.getUnknownMember()
283+
result = this.getUnknownArrayElement()
242284
}
243285

244286
/**
@@ -790,6 +832,11 @@ module API {
790832
not DataFlow::PseudoProperties::isPseudoProperty(prop)
791833
)
792834
or
835+
exists(DataFlow::ContentSet contents |
836+
SummaryTypeTracker::basicStoreStep(rhs, pred.getALocalUse(), contents) and
837+
lbl = Label::content(contents.getAStoreContent())
838+
)
839+
or
793840
exists(DataFlow::FunctionNode fn |
794841
fn = pred and
795842
lbl = Label::return()
@@ -982,6 +1029,11 @@ module API {
9821029
// avoid generating member edges like "$arrayElement$"
9831030
not DataFlow::PseudoProperties::isPseudoProperty(prop)
9841031
)
1032+
or
1033+
exists(DataFlow::ContentSet contents |
1034+
SummaryTypeTracker::basicLoadStep(pred.getALocalUse(), ref, contents) and
1035+
lbl = Label::content(contents.getAStoreContent())
1036+
)
9851037
)
9861038
or
9871039
exists(DataFlow::Node def, DataFlow::FunctionNode fn |
@@ -1199,8 +1251,6 @@ module API {
11991251
t = useStep(nd, promisified, boundArgs, prop, result)
12001252
}
12011253

1202-
private import semmle.javascript.dataflow.internal.StepSummary
1203-
12041254
/**
12051255
* Holds if `nd`, which is a use of an API-graph node, flows in zero or more potentially
12061256
* inter-procedural steps to some intermediate node, and then from that intermediate node to
@@ -1458,8 +1508,21 @@ module API {
14581508
bindingset[result]
14591509
LabelMember member(string m) { result.getProperty() = m }
14601510

1461-
/** Gets the `member` edge label for the unknown member. */
1462-
LabelUnknownMember unknownMember() { any() }
1511+
/** Gets the `content` edge label for content `c`. */
1512+
LabelContent content(ContentPrivate::Content c) { result.getContent() = c }
1513+
1514+
/**
1515+
* Gets the edge label for an unknown member.
1516+
*
1517+
* Currently this is represented the same way as an unknown array element, but this may
1518+
* change in the future.
1519+
*/
1520+
ApiLabel unknownMember() { result = arrayElement() }
1521+
1522+
/**
1523+
* Gets the edge label for an unknown array element.
1524+
*/
1525+
LabelContent arrayElement() { result.getContent().isUnknownArrayElement() }
14631526

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

1548+
pragma[nomagic]
1549+
private predicate isEnumeratedPropName(DataFlow::Node node) {
1550+
node.getAPredecessor*() instanceof EnumeratedPropName
1551+
}
1552+
14851553
/** Gets the `member` edge label for the given property reference. */
14861554
ApiLabel memberFromRef(DataFlow::PropRef pr) {
14871555
exists(string pn | pn = pr.getPropertyName() or pn = getIndirectPropName(pr) |
@@ -1493,7 +1561,9 @@ module API {
14931561
or
14941562
not exists(pr.getPropertyName()) and
14951563
not exists(getIndirectPropName(pr)) and
1496-
result = unknownMember()
1564+
// Avoid assignments in an extend-like pattern
1565+
not isEnumeratedPropName(pr.getPropertyNameExpr().flow()) and
1566+
result = arrayElement()
14971567
}
14981568

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

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

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

15241594
/** Gets the label for an edge leading from a value `D` to any class that has `D` as a decorator. */
15251595
LabelDecoratedClass decoratedClass() { any() }
@@ -1542,18 +1612,12 @@ module API {
15421612
exists(Impl::MkModuleImport(mod))
15431613
} or
15441614
MkLabelInstance() or
1545-
MkLabelMember(string prop) {
1546-
exports(_, prop, _) or
1547-
exists(any(DataFlow::ClassNode c).getInstanceMethod(prop)) or
1548-
prop = "exports" or
1549-
prop = any(CanonicalName c).getName() or
1550-
prop = any(DataFlow::PropRef p).getPropertyName() or
1551-
exists(Impl::MkTypeUse(_, prop)) or
1552-
exists(any(Module m).getAnExportedValue(prop)) or
1553-
PreCallGraphStep::loadStep(_, _, prop) or
1554-
PreCallGraphStep::storeStep(_, _, prop)
1615+
MkLabelContent(DataFlow::Content content) or
1616+
MkLabelMember(string name) {
1617+
name instanceof PropertyName
1618+
or
1619+
exists(Impl::MkTypeUse(_, name))
15551620
} or
1556-
MkLabelUnknownMember() or
15571621
MkLabelParameter(int i) {
15581622
i =
15591623
[0 .. max(int args |
@@ -1564,8 +1628,6 @@ module API {
15641628
} or
15651629
MkLabelReceiver() or
15661630
MkLabelReturn() or
1567-
MkLabelPromised() or
1568-
MkLabelPromisedError() or
15691631
MkLabelDecoratedClass() or
15701632
MkLabelDecoratedMember() or
15711633
MkLabelDecoratedParameter() or
@@ -1585,13 +1647,13 @@ module API {
15851647
}
15861648

15871649
/** A label that gets a promised value. */
1588-
class LabelPromised extends ApiLabel, MkLabelPromised {
1589-
override string toString() { result = "getPromised()" }
1650+
deprecated class LabelPromised extends ApiLabel {
1651+
LabelPromised() { this = MkLabelContent(ContentPrivate::MkPromiseValue()) }
15901652
}
15911653

15921654
/** A label that gets a rejected promise. */
1593-
class LabelPromisedError extends ApiLabel, MkLabelPromisedError {
1594-
override string toString() { result = "getPromisedError()" }
1655+
deprecated class LabelPromisedError extends ApiLabel {
1656+
LabelPromisedError() { this = MkLabelContent(ContentPrivate::MkPromiseError()) }
15951657
}
15961658

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

1682+
/** A label for a content. */
1683+
class LabelContent extends ApiLabel, MkLabelContent {
1684+
private DataFlow::Content content;
1685+
1686+
LabelContent() {
1687+
this = MkLabelContent(content) and
1688+
// Property names are represented by LabelMember to ensure additional property
1689+
// names from PreCallGraph step are included, as well as those from MkTypeUse.
1690+
not content instanceof ContentPrivate::MkPropertyContent
1691+
}
1692+
1693+
/** Gets the content associated with this label. */
1694+
DataFlow::Content getContent() { result = content }
1695+
1696+
private string specialisedToString() {
1697+
content instanceof ContentPrivate::MkPromiseValue and result = "getPromised()"
1698+
or
1699+
content instanceof ContentPrivate::MkPromiseError and result = "getPromisedError()"
1700+
or
1701+
content instanceof ContentPrivate::MkArrayElementUnknown and result = "getArrayElement()"
1702+
}
1703+
1704+
override string toString() {
1705+
result = this.specialisedToString()
1706+
or
1707+
not exists(this.specialisedToString()) and
1708+
result = "getContent(" + content + ")"
1709+
}
1710+
}
1711+
16201712
/** A label for the member named `prop`. */
16211713
class LabelMember extends ApiLabel, MkLabelMember {
1622-
string prop;
1714+
private string prop;
16231715

16241716
LabelMember() { this = MkLabelMember(prop) }
16251717

@@ -1630,10 +1722,8 @@ module API {
16301722
}
16311723

16321724
/** A label for a member with an unknown name. */
1633-
class LabelUnknownMember extends ApiLabel, MkLabelUnknownMember {
1634-
LabelUnknownMember() { this = MkLabelUnknownMember() }
1635-
1636-
override string toString() { result = "getUnknownMember()" }
1725+
deprecated class LabelUnknownMember extends LabelContent {
1726+
LabelUnknownMember() { this.getContent().isUnknownArrayElement() }
16371727
}
16381728

16391729
/** A label for parameter `i`. */

javascript/ql/lib/semmle/javascript/dataflow/internal/Contents.qll

+10
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,16 @@ module Private {
5757
this = getAPreciseArrayIndex().toString()
5858
or
5959
isAccessPathTokenPresent("Member", this)
60+
or
61+
this = any(ImportSpecifier spec).getImportedName()
62+
or
63+
this = any(ExportSpecifier n).getExportedName()
64+
or
65+
this = any(ExportNamedDeclaration d).getAnExportedDecl().getName()
66+
or
67+
this = any(MemberDefinition m).getName()
68+
or
69+
this = ["exports", "default"]
6070
}
6171

6272
/** Gets the array index corresponding to this property name. */

javascript/ql/lib/semmle/javascript/frameworks/D3.qll

+1-1
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ module D3 {
8080
or
8181
this = d3Selection().getMember("node").getReturn().asSource()
8282
or
83-
this = d3Selection().getMember("nodes").getReturn().getUnknownMember().asSource()
83+
this = d3Selection().getMember("nodes").getReturn().getArrayElement().asSource()
8484
}
8585
}
8686

javascript/ql/lib/semmle/javascript/frameworks/Puppeteer.qll

+3-3
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ module Puppeteer {
3232
or
3333
result = [browser(), context()].getMember("newPage").getReturn().getPromised()
3434
or
35-
result = [browser(), context()].getMember("pages").getReturn().getPromised().getUnknownMember()
35+
result = [browser(), context()].getMember("pages").getReturn().getPromised().getArrayElement()
3636
or
3737
result = target().getMember("page").getReturn().getPromised()
3838
}
@@ -45,7 +45,7 @@ module Puppeteer {
4545
or
4646
result = [page(), browser()].getMember("target").getReturn()
4747
or
48-
result = context().getMember("targets").getReturn().getUnknownMember()
48+
result = context().getMember("targets").getReturn().getArrayElement()
4949
or
5050
result = target().getMember("opener").getReturn()
5151
}
@@ -58,7 +58,7 @@ module Puppeteer {
5858
or
5959
result = [page(), target()].getMember("browserContext").getReturn()
6060
or
61-
result = browser().getMember("browserContexts").getReturn().getUnknownMember()
61+
result = browser().getMember("browserContexts").getReturn().getArrayElement()
6262
or
6363
result = browser().getMember("createIncognitoBrowserContext").getReturn().getPromised()
6464
or

javascript/ql/lib/semmle/javascript/frameworks/Vuex.qll

+1-2
Original file line numberDiff line numberDiff line change
@@ -104,8 +104,7 @@ module Vuex {
104104
storeName = this.getNamespace() + localName
105105
or
106106
// mapGetters(['foo', 'bar'])
107-
this.getLastParameter().getUnknownMember().getAValueReachingSink().getStringValue() =
108-
localName and
107+
this.getLastParameter().getArrayElement().getAValueReachingSink().getStringValue() = localName and
109108
storeName = this.getNamespace() + localName
110109
or
111110
// mapGetters({foo: 'bar'})

javascript/ql/lib/semmle/javascript/frameworks/data/internal/ApiGraphModelsSpecific.qll

+3-8
Original file line numberDiff line numberDiff line change
@@ -162,8 +162,8 @@ API::Node getExtraSuccessorFromNode(API::Node node, AccessPathTokenBase token) {
162162
token.getName() = "Awaited" and
163163
result = node.getPromised()
164164
or
165-
token.getName() = "ArrayElement" and
166-
result = node.getMember(DataFlow::PseudoProperties::arrayElement())
165+
token.getName() = ["ArrayElement", "Element"] and
166+
result = node.getArrayElement()
167167
or
168168
token.getName() = "Element" and
169169
result = node.getMember(DataFlow::PseudoProperties::arrayLikeElement())
@@ -172,11 +172,6 @@ API::Node getExtraSuccessorFromNode(API::Node node, AccessPathTokenBase token) {
172172
token.getName() = "MapValue" and
173173
result = node.getMember(DataFlow::PseudoProperties::mapValueAll())
174174
or
175-
// Currently we need to include the "unknown member" for ArrayElement and Element since
176-
// API graphs do not use store/load steps for arrays
177-
token.getName() = ["ArrayElement", "Element"] and
178-
result = node.getUnknownMember()
179-
or
180175
token.getName() = "Parameter" and
181176
token.getAnArgument() = "this" and
182177
result = node.getReceiver()
@@ -373,7 +368,7 @@ bindingset[pred]
373368
predicate apiGraphHasEdge(API::Node pred, string path, API::Node succ) {
374369
exists(string name | succ = pred.getMember(name) and path = "Member[" + name + "]")
375370
or
376-
succ = pred.getUnknownMember() and path = "AnyMember"
371+
succ = pred.getUnknownArrayElement() and path = "ArrayElement"
377372
or
378373
succ = pred.getInstance() and path = "Instance"
379374
or

javascript/ql/lib/semmle/javascript/internal/CachedStages.qll

+1-2
Original file line numberDiff line numberDiff line change
@@ -297,13 +297,12 @@ module Stages {
297297
exists(
298298
API::moduleImport("foo")
299299
.getMember("bar")
300-
.getUnknownMember()
300+
.getArrayElement()
301301
.getAMember()
302302
.getAParameter()
303303
.getPromised()
304304
.getReturn()
305305
.getParameter(2)
306-
.getUnknownMember()
307306
.getInstance()
308307
.getReceiver()
309308
.getForwardingFunction()

0 commit comments

Comments
 (0)