Merge pull request #19012 from asgerf/js/api-graph-array-element

JS: Make API graphs use steps from summaries
This commit is contained in:
Asger F
2025-03-18 18:03:43 +01:00
committed by GitHub
18 changed files with 181 additions and 64 deletions

View File

@@ -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-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/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[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/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.AnyMember.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"] - ["@tanstack/react-query", "Member[useQuery]", "Argument[0].Member[queryFn].ReturnValue.Awaited", "ReturnValue.Member[data]", "value"]

View File

@@ -8,6 +8,10 @@
import javascript import javascript
private import semmle.javascript.dataflow.internal.FlowSteps as FlowSteps private import semmle.javascript.dataflow.internal.FlowSteps as FlowSteps
private import semmle.javascript.dataflow.internal.PreCallGraphStep 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 private import internal.CachedStages
/** /**
@@ -220,15 +224,53 @@ module API {
} }
/** /**
* Gets a node representing a member of this API component where the name of the member is * DEPRECATED. Use either `getArrayElement()` or `getAMember()` instead.
* not known statically. */
deprecated Node getUnknownMember() { result = this.getArrayElement() }
/**
* Gets an array element of unknown index.
*/ */
cached cached
Node getUnknownMember() { Node getUnknownArrayElement() {
Stages::ApiStage::ref() and Stages::ApiStage::ref() and
result = this.getASuccessor(Label::unknownMember()) result = this.getASuccessor(Label::content(ContentPrivate::MkArrayElementUnknown()))
} }
cached
private Node getContentRaw(DataFlow::Content content) {
Stages::ApiStage::ref() and
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 * Gets a node representing a member of this API component where the name of the member may
* or may not be known statically. * or may not be known statically.
@@ -238,7 +280,7 @@ module API {
Stages::ApiStage::ref() and Stages::ApiStage::ref() and
result = this.getMember(_) result = this.getMember(_)
or or
result = this.getUnknownMember() result = this.getUnknownArrayElement()
} }
/** /**
@@ -790,6 +832,11 @@ module API {
not DataFlow::PseudoProperties::isPseudoProperty(prop) not DataFlow::PseudoProperties::isPseudoProperty(prop)
) )
or or
exists(DataFlow::ContentSet contents |
SummaryTypeTracker::basicStoreStep(rhs, pred.getALocalUse(), contents) and
lbl = Label::content(contents.getAStoreContent())
)
or
exists(DataFlow::FunctionNode fn | exists(DataFlow::FunctionNode fn |
fn = pred and fn = pred and
lbl = Label::return() lbl = Label::return()
@@ -982,6 +1029,11 @@ module API {
// avoid generating member edges like "$arrayElement$" // avoid generating member edges like "$arrayElement$"
not DataFlow::PseudoProperties::isPseudoProperty(prop) not DataFlow::PseudoProperties::isPseudoProperty(prop)
) )
or
exists(DataFlow::ContentSet contents |
SummaryTypeTracker::basicLoadStep(pred.getALocalUse(), ref, contents) and
lbl = Label::content(contents.getAStoreContent())
)
) )
or or
exists(DataFlow::Node def, DataFlow::FunctionNode fn | exists(DataFlow::Node def, DataFlow::FunctionNode fn |
@@ -1199,8 +1251,6 @@ module API {
t = useStep(nd, promisified, boundArgs, prop, result) 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 * 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 * inter-procedural steps to some intermediate node, and then from that intermediate node to
@@ -1458,8 +1508,21 @@ module API {
bindingset[result] bindingset[result]
LabelMember member(string m) { result.getProperty() = m } LabelMember member(string m) { result.getProperty() = m }
/** Gets the `member` edge label for the unknown member. */ /** Gets the `content` edge label for content `c`. */
LabelUnknownMember unknownMember() { any() } 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, * Gets a property name referred to by the given dynamic property access,
@@ -1482,6 +1545,11 @@ module API {
result = unique(string s | s = getAnIndirectPropName(ref)) 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. */ /** Gets the `member` edge label for the given property reference. */
ApiLabel memberFromRef(DataFlow::PropRef pr) { ApiLabel memberFromRef(DataFlow::PropRef pr) {
exists(string pn | pn = pr.getPropertyName() or pn = getIndirectPropName(pr) | exists(string pn | pn = pr.getPropertyName() or pn = getIndirectPropName(pr) |
@@ -1493,7 +1561,9 @@ module API {
or or
not exists(pr.getPropertyName()) and not exists(pr.getPropertyName()) and
not exists(getIndirectPropName(pr)) 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. */ /** Gets the `instance` edge label. */
@@ -1516,10 +1586,10 @@ module API {
LabelForwardingFunction forwardingFunction() { any() } LabelForwardingFunction forwardingFunction() { any() }
/** Gets the `promised` edge label connecting a promise to its contained value. */ /** 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. */ /** 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. */ /** Gets the label for an edge leading from a value `D` to any class that has `D` as a decorator. */
LabelDecoratedClass decoratedClass() { any() } LabelDecoratedClass decoratedClass() { any() }
@@ -1542,18 +1612,12 @@ module API {
exists(Impl::MkModuleImport(mod)) exists(Impl::MkModuleImport(mod))
} or } or
MkLabelInstance() or MkLabelInstance() or
MkLabelMember(string prop) { MkLabelContent(DataFlow::Content content) or
exports(_, prop, _) or MkLabelMember(string name) {
exists(any(DataFlow::ClassNode c).getInstanceMethod(prop)) or name instanceof PropertyName
prop = "exports" or or
prop = any(CanonicalName c).getName() or exists(Impl::MkTypeUse(_, name))
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)
} or } or
MkLabelUnknownMember() or
MkLabelParameter(int i) { MkLabelParameter(int i) {
i = i =
[0 .. max(int args | [0 .. max(int args |
@@ -1564,8 +1628,6 @@ module API {
} or } or
MkLabelReceiver() or MkLabelReceiver() or
MkLabelReturn() or MkLabelReturn() or
MkLabelPromised() or
MkLabelPromisedError() or
MkLabelDecoratedClass() or MkLabelDecoratedClass() or
MkLabelDecoratedMember() or MkLabelDecoratedMember() or
MkLabelDecoratedParameter() or MkLabelDecoratedParameter() or
@@ -1585,13 +1647,13 @@ module API {
} }
/** A label that gets a promised value. */ /** A label that gets a promised value. */
class LabelPromised extends ApiLabel, MkLabelPromised { deprecated class LabelPromised extends ApiLabel {
override string toString() { result = "getPromised()" } LabelPromised() { this = MkLabelContent(ContentPrivate::MkPromiseValue()) }
} }
/** A label that gets a rejected promise. */ /** A label that gets a rejected promise. */
class LabelPromisedError extends ApiLabel, MkLabelPromisedError { deprecated class LabelPromisedError extends ApiLabel {
override string toString() { result = "getPromisedError()" } LabelPromisedError() { this = MkLabelContent(ContentPrivate::MkPromiseError()) }
} }
/** A label that gets the return value of a function. */ /** A label that gets the return value of a function. */
@@ -1617,9 +1679,39 @@ module API {
override string toString() { result = "getInstance()" } 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`. */ /** A label for the member named `prop`. */
class LabelMember extends ApiLabel, MkLabelMember { class LabelMember extends ApiLabel, MkLabelMember {
string prop; private string prop;
LabelMember() { this = MkLabelMember(prop) } LabelMember() { this = MkLabelMember(prop) }
@@ -1630,10 +1722,8 @@ module API {
} }
/** A label for a member with an unknown name. */ /** A label for a member with an unknown name. */
class LabelUnknownMember extends ApiLabel, MkLabelUnknownMember { deprecated class LabelUnknownMember extends LabelContent {
LabelUnknownMember() { this = MkLabelUnknownMember() } LabelUnknownMember() { this.getContent().isUnknownArrayElement() }
override string toString() { result = "getUnknownMember()" }
} }
/** A label for parameter `i`. */ /** A label for parameter `i`. */

View File

@@ -57,6 +57,16 @@ module Private {
this = getAPreciseArrayIndex().toString() this = getAPreciseArrayIndex().toString()
or or
isAccessPathTokenPresent("Member", this) 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. */ /** Gets the array index corresponding to this property name. */

View File

@@ -80,7 +80,7 @@ module D3 {
or or
this = d3Selection().getMember("node").getReturn().asSource() this = d3Selection().getMember("node").getReturn().asSource()
or or
this = d3Selection().getMember("nodes").getReturn().getUnknownMember().asSource() this = d3Selection().getMember("nodes").getReturn().getArrayElement().asSource()
} }
} }

View File

@@ -32,7 +32,7 @@ module Puppeteer {
or or
result = [browser(), context()].getMember("newPage").getReturn().getPromised() result = [browser(), context()].getMember("newPage").getReturn().getPromised()
or or
result = [browser(), context()].getMember("pages").getReturn().getPromised().getUnknownMember() result = [browser(), context()].getMember("pages").getReturn().getPromised().getArrayElement()
or or
result = target().getMember("page").getReturn().getPromised() result = target().getMember("page").getReturn().getPromised()
} }
@@ -45,7 +45,7 @@ module Puppeteer {
or or
result = [page(), browser()].getMember("target").getReturn() result = [page(), browser()].getMember("target").getReturn()
or or
result = context().getMember("targets").getReturn().getUnknownMember() result = context().getMember("targets").getReturn().getArrayElement()
or or
result = target().getMember("opener").getReturn() result = target().getMember("opener").getReturn()
} }
@@ -58,7 +58,7 @@ module Puppeteer {
or or
result = [page(), target()].getMember("browserContext").getReturn() result = [page(), target()].getMember("browserContext").getReturn()
or or
result = browser().getMember("browserContexts").getReturn().getUnknownMember() result = browser().getMember("browserContexts").getReturn().getArrayElement()
or or
result = browser().getMember("createIncognitoBrowserContext").getReturn().getPromised() result = browser().getMember("createIncognitoBrowserContext").getReturn().getPromised()
or or

View File

@@ -104,8 +104,7 @@ module Vuex {
storeName = this.getNamespace() + localName storeName = this.getNamespace() + localName
or or
// mapGetters(['foo', 'bar']) // mapGetters(['foo', 'bar'])
this.getLastParameter().getUnknownMember().getAValueReachingSink().getStringValue() = this.getLastParameter().getArrayElement().getAValueReachingSink().getStringValue() = localName and
localName and
storeName = this.getNamespace() + localName storeName = this.getNamespace() + localName
or or
// mapGetters({foo: 'bar'}) // mapGetters({foo: 'bar'})

View File

@@ -162,8 +162,8 @@ API::Node getExtraSuccessorFromNode(API::Node node, AccessPathTokenBase token) {
token.getName() = "Awaited" and token.getName() = "Awaited" and
result = node.getPromised() result = node.getPromised()
or or
token.getName() = "ArrayElement" and token.getName() = ["ArrayElement", "Element"] and
result = node.getMember(DataFlow::PseudoProperties::arrayElement()) result = node.getArrayElement()
or or
token.getName() = "Element" and token.getName() = "Element" and
result = node.getMember(DataFlow::PseudoProperties::arrayLikeElement()) result = node.getMember(DataFlow::PseudoProperties::arrayLikeElement())
@@ -172,11 +172,6 @@ API::Node getExtraSuccessorFromNode(API::Node node, AccessPathTokenBase token) {
token.getName() = "MapValue" and token.getName() = "MapValue" and
result = node.getMember(DataFlow::PseudoProperties::mapValueAll()) result = node.getMember(DataFlow::PseudoProperties::mapValueAll())
or 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.getName() = "Parameter" and
token.getAnArgument() = "this" and token.getAnArgument() = "this" and
result = node.getReceiver() result = node.getReceiver()
@@ -373,7 +368,7 @@ bindingset[pred]
predicate apiGraphHasEdge(API::Node pred, string path, API::Node succ) { predicate apiGraphHasEdge(API::Node pred, string path, API::Node succ) {
exists(string name | succ = pred.getMember(name) and path = "Member[" + name + "]") exists(string name | succ = pred.getMember(name) and path = "Member[" + name + "]")
or or
succ = pred.getUnknownMember() and path = "AnyMember" succ = pred.getUnknownArrayElement() and path = "ArrayElement"
or or
succ = pred.getInstance() and path = "Instance" succ = pred.getInstance() and path = "Instance"
or or

View File

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

View File

@@ -96,7 +96,8 @@ class ArrayConstructorSummary extends SummarizedCallable {
ArrayConstructorSummary() { this = "Array constructor" } ArrayConstructorSummary() { this = "Array constructor" }
override DataFlow::InvokeNode getACallSimple() { override DataFlow::InvokeNode getACallSimple() {
result = arrayConstructorRef().getAnInvocation() result = arrayConstructorRef().getAnInvocation() and
result.getNumArgument() > 1
} }
override predicate propagatesFlow(string input, string output, boolean preservesValue) { override predicate propagatesFlow(string input, string output, boolean preservesValue) {

View File

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

View File

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

View File

@@ -72,7 +72,7 @@ module Execa {
override predicate isShellInterpreted(DataFlow::Node arg) { override predicate isShellInterpreted(DataFlow::Node arg) {
// if shell: true then first and second args are sinks // if shell: true then first and second args are sinks
// options can be third argument // 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)) isExecaShellEnable(this.getParameter(2))
or or
// options can be second argument // options can be second argument

View File

@@ -2,4 +2,4 @@ const MyStream = require('classes').MyStream;
var s = new MyStream(); var s = new MyStream();
for (let m of ["write"]) for (let m of ["write"])
s[m]("Hello, world!"); /* use=moduleImport("classes").getMember("exports").getMember("MyStream").getInstance().getUnknownMember() */ s[m]("Hello, world!"); /* use=moduleImport("classes").getMember("exports").getMember("MyStream").getInstance().getArrayElement() */

View File

@@ -81,6 +81,10 @@ taintFlow
| test.js:272:6:272:40 | new MyS ... ource() | test.js:272:6:272:40 | new MyS ... ource() | | test.js:272:6:272:40 | new MyS ... ource() | test.js:272:6:272:40 | new MyS ... ource() |
| test.js:274:6:274:39 | testlib ... eName() | test.js:274:6:274:39 | testlib ... eName() | | test.js:274:6:274:39 | testlib ... eName() | test.js:274:6:274:39 | testlib ... eName() |
| test.js:277:8:277:31 | "danger ... .danger | test.js:277:8:277:31 | "danger ... .danger | | test.js:277:8:277:31 | "danger ... .danger | test.js:277:8:277:31 | "danger ... .danger |
| test.js:284:8:284:16 | source[0] | test.js:284:8:284:16 | source[0] |
| test.js:285:8:285:19 | source.pop() | test.js:285:8:285:19 | source.pop() |
| test.js:286:18:286:18 | e | test.js:286:28:286:28 | e |
| test.js:287:14:287:14 | e | test.js:287:24:287:24 | e |
isSink isSink
| test.js:54:18:54:25 | source() | test-sink | | test.js:54:18:54:25 | source() | test-sink |
| test.js:55:22:55:29 | source() | test-sink | | test.js:55:22:55:29 | source() | test-sink |

View File

@@ -10,6 +10,7 @@ extensions:
- ['testlib', 'Member[MethodDecorator].DecoratedMember.Parameter[0]', 'test-source'] - ['testlib', 'Member[MethodDecorator].DecoratedMember.Parameter[0]', 'test-source']
- ['testlib', 'Member[ParamDecoratorSource].DecoratedParameter', 'test-source'] - ['testlib', 'Member[ParamDecoratorSource].DecoratedParameter', 'test-source']
- ['testlib', 'Member[getSource].ReturnValue', 'test-source'] - ['testlib', 'Member[getSource].ReturnValue', 'test-source']
- ['testlib', 'Member[getSourceArray].ReturnValue.ArrayElement', 'test-source']
- ['(testlib)', 'Member[parenthesizedPackageName].ReturnValue', 'test-source'] - ['(testlib)', 'Member[parenthesizedPackageName].ReturnValue', 'test-source']
- ['danger-constant', 'Member[danger]', 'test-source'] - ['danger-constant', 'Member[danger]', 'test-source']

View File

@@ -278,3 +278,11 @@ function dangerConstant() {
sink("danger-constant".safe); // OK sink("danger-constant".safe); // OK
sink("danger-constant"); // OK sink("danger-constant"); // OK
} }
function arraySource() {
const source = testlib.getSourceArray();
sink(source[0]); // NOT OK
sink(source.pop()); // NOT OK
source.forEach(e => sink(e)); // NOT OK
source.map(e => sink(e)); // NOT OK
}

View File

@@ -16,6 +16,7 @@
| testReactUseQueries.jsx:37:25:37:38 | repoQuery.data | testReactUseQueries.jsx:4:26:4:53 | fetch(' ... e.com') | testReactUseQueries.jsx:37:25:37:38 | repoQuery.data | Cross-site scripting vulnerability due to $@. | testReactUseQueries.jsx:4:26:4:53 | fetch(' ... e.com') | user-provided value | | testReactUseQueries.jsx:37:25:37:38 | repoQuery.data | testReactUseQueries.jsx:4:26:4:53 | fetch(' ... e.com') | testReactUseQueries.jsx:37:25:37:38 | repoQuery.data | Cross-site scripting vulnerability due to $@. | testReactUseQueries.jsx:4:26:4:53 | fetch(' ... e.com') | user-provided value |
| testUseQueries2.vue:40:10:40:23 | v-html=data3 | testUseQueries2.vue:6:28:6:63 | fetch(" ... ntent") | testUseQueries2.vue:40:10:40:23 | v-html=data3 | Cross-site scripting vulnerability due to $@. | testUseQueries2.vue:6:28:6:63 | fetch(" ... ntent") | user-provided value | | testUseQueries2.vue:40:10:40:23 | v-html=data3 | testUseQueries2.vue:6:28:6:63 | fetch(" ... ntent") | testUseQueries2.vue:40:10:40:23 | v-html=data3 | Cross-site scripting vulnerability due to $@. | testUseQueries2.vue:6:28:6:63 | fetch(" ... ntent") | user-provided value |
| testUseQueries2.vue:40:10:40:23 | v-html=data3 | testUseQueries2.vue:12:28:12:41 | fetch("${id}") | testUseQueries2.vue:40:10:40:23 | v-html=data3 | Cross-site scripting vulnerability due to $@. | testUseQueries2.vue:12:28:12:41 | fetch("${id}") | user-provided value | | testUseQueries2.vue:40:10:40:23 | v-html=data3 | testUseQueries2.vue:12:28:12:41 | fetch("${id}") | testUseQueries2.vue:40:10:40:23 | v-html=data3 | Cross-site scripting vulnerability due to $@. | testUseQueries2.vue:12:28:12:41 | fetch("${id}") | user-provided value |
| testUseQueries.vue:25:10:25:23 | v-html=data2 | testUseQueries.vue:11:36:11:49 | fetch("${id}") | testUseQueries.vue:25:10:25:23 | v-html=data2 | Cross-site scripting vulnerability due to $@. | testUseQueries.vue:11:36:11:49 | fetch("${id}") | user-provided value |
edges edges
| test.jsx:5:11:5:63 | response | test.jsx:6:24:6:31 | response | provenance | | | test.jsx:5:11:5:63 | response | test.jsx:6:24:6:31 | response | provenance | |
| test.jsx:5:22:5:63 | await f ... ntent") | test.jsx:5:11:5:63 | response | provenance | | | test.jsx:5:22:5:63 | await f ... ntent") | test.jsx:5:11:5:63 | response | provenance | |
@@ -88,6 +89,12 @@ edges
| testUseQueries2.vue:13:12:13:19 | response | testUseQueries2.vue:13:12:13:26 | response.json() | provenance | | | testUseQueries2.vue:13:12:13:19 | response | testUseQueries2.vue:13:12:13:26 | response.json() | provenance | |
| testUseQueries2.vue:13:12:13:26 | response.json() | testUseQueries2.vue:33:22:33:36 | results[0].data | provenance | | | testUseQueries2.vue:13:12:13:26 | response.json() | testUseQueries2.vue:33:22:33:36 | results[0].data | provenance | |
| testUseQueries2.vue:33:22:33:36 | results[0].data | testUseQueries2.vue:40:10:40:23 | v-html=data3 | provenance | | | testUseQueries2.vue:33:22:33:36 | results[0].data | testUseQueries2.vue:40:10:40:23 | v-html=data3 | provenance | |
| testUseQueries.vue:11:19:11:49 | response | testUseQueries.vue:12:20:12:27 | response | provenance | |
| testUseQueries.vue:11:30:11:49 | await fetch("${id}") | testUseQueries.vue:11:19:11:49 | response | provenance | |
| testUseQueries.vue:11:36:11:49 | fetch("${id}") | testUseQueries.vue:11:30:11:49 | await fetch("${id}") | provenance | |
| testUseQueries.vue:12:20:12:27 | response | testUseQueries.vue:12:20:12:34 | response.json() | provenance | |
| testUseQueries.vue:12:20:12:34 | response.json() | testUseQueries.vue:18:22:18:36 | results[0].data | provenance | |
| testUseQueries.vue:18:22:18:36 | results[0].data | testUseQueries.vue:25:10:25:23 | v-html=data2 | provenance | |
nodes nodes
| test.jsx:5:11:5:63 | response | semmle.label | response | | test.jsx:5:11:5:63 | response | semmle.label | response |
| test.jsx:5:22:5:63 | await f ... ntent") | semmle.label | await f ... ntent") | | test.jsx:5:22:5:63 | await f ... ntent") | semmle.label | await f ... ntent") |
@@ -174,4 +181,11 @@ nodes
| testUseQueries2.vue:13:12:13:26 | response.json() | semmle.label | response.json() | | testUseQueries2.vue:13:12:13:26 | response.json() | semmle.label | response.json() |
| testUseQueries2.vue:33:22:33:36 | results[0].data | semmle.label | results[0].data | | testUseQueries2.vue:33:22:33:36 | results[0].data | semmle.label | results[0].data |
| testUseQueries2.vue:40:10:40:23 | v-html=data3 | semmle.label | v-html=data3 | | testUseQueries2.vue:40:10:40:23 | v-html=data3 | semmle.label | v-html=data3 |
| testUseQueries.vue:11:19:11:49 | response | semmle.label | response |
| testUseQueries.vue:11:30:11:49 | await fetch("${id}") | semmle.label | await fetch("${id}") |
| testUseQueries.vue:11:36:11:49 | fetch("${id}") | semmle.label | fetch("${id}") |
| testUseQueries.vue:12:20:12:27 | response | semmle.label | response |
| testUseQueries.vue:12:20:12:34 | response.json() | semmle.label | response.json() |
| testUseQueries.vue:18:22:18:36 | results[0].data | semmle.label | results[0].data |
| testUseQueries.vue:25:10:25:23 | v-html=data2 | semmle.label | v-html=data2 |
subpaths subpaths

View File

@@ -8,7 +8,7 @@ export default {
queries: ids.map((id) => ({ queries: ids.map((id) => ({
queryKey: ['post', id], queryKey: ['post', id],
queryFn: async () => { queryFn: async () => {
const response = await fetch("${id}"); // $ MISSING: Source const response = await fetch("${id}"); // $ Source
return response.json(); return response.json();
}, },
staleTime: Infinity, staleTime: Infinity,
@@ -22,6 +22,6 @@ export default {
<template> <template>
<VueQueryClientProvider :client="queryClient"> <VueQueryClientProvider :client="queryClient">
<div v-html="data2"></div> <!--$ MISSING: Alert --> <div v-html="data2"></div> <!--$ Alert -->
</VueQueryClientProvider> </VueQueryClientProvider>
</template> </template>