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
Original file line number Diff line number Diff line change
Expand Up @@ -544,6 +544,25 @@ class ToSpliced extends SummarizedCallable {
}
}

class With extends SummarizedCallable {
With() { this = "Array#with" }

override InstanceCall getACallSimple() { result.getMethodName() = "with" }

override predicate propagatesFlow(string input, string output, boolean preservesValue) {
preservesValue = true and
(
// Copy all elements from the original array to the new array
input = "Argument[this].WithArrayElement" and
output = "ReturnValue"
or
// Replace the value at the specified index
input = "Argument[1]" and
output = "ReturnValue.ArrayElement"
)
}
}

class ArrayCoercionPackage extends FunctionalPackageSummary {
ArrayCoercionPackage() { this = "ArrayCoercionPackage" }

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,3 +49,10 @@ string getAnArrayContent() {
// Values stored at an unknown index
result = "ArrayElement[?]"
}

/**
* Gets an argument position up to a certain limit.
*
* This can be used to generate flow summaries that should preserve such positions.
*/
int getAnArgumentPosition() { result = [0 .. 10] }
Original file line number Diff line number Diff line change
Expand Up @@ -368,3 +368,29 @@ private class PromiseWithResolversLike extends SummarizedCallable {
)
}
}

class PromiseTry extends DataFlow::SummarizedCallable {
PromiseTry() { this = "Promise.try()" }

override DataFlow::CallNode getACallSimple() {
result = promiseConstructorRef().getAMemberCall(["try", "attempt"])
or
result = DataFlow::moduleImport(["p-try", "es6-promise-try"]).getACall()
}

override predicate propagatesFlow(string input, string output, boolean preservesValue) {
preservesValue = true and
(
exists(int i | i = getAnArgumentPosition() |
input = "Argument[" + (i + 1) + "]" and
output = "Argument[0].Parameter[" + i + "]"
)
Comment on lines +384 to +387
Copy link
Contributor

@Napalys Napalys Sep 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a quick question for my understanding. We would not have flow if a lambda would have more then 10 arguments?
Also what happens if we have ...args(arbitrary number of arguments), will it work?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct on both accounts

or
input = "Argument[0].ReturnValue" and
output = "ReturnValue.Awaited"
or
input = "Argument[0].ReturnValue[exception]" and
output = "ReturnValue.Awaited[error]"
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
---
category: minorAnalysis
---
* Data flow is now tracked through the `Promise.try` and `Array.prototype.with` functions.
25 changes: 25 additions & 0 deletions javascript/ql/test/library-tests/TripleDot/array-with.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
function t1() {
const arr = [1, 2, 3];
const newArr = arr.with(1, source('with.1'));
sink(newArr[1]); // $ hasValueFlow=with.1
}

function t2() {
const arr = [source('with.2.1'), 2, source('with.2.3')];
const newArr = arr.with(1, 'replaced');
sink(newArr[0]); // $ hasValueFlow=with.2.1
sink(newArr[2]); // $ hasValueFlow=with.2.3
}

function t3() {
const arr = [1, 2, 3];
const index = source('with.3.index');
const newArr = arr.with(index, 'new value');
// No assertions here as the index is tainted, not the value
}

function t4() {
const arr = [1, 2, 3];
const newArr = arr.with(1, source('with.4'));
sink(arr[1]); // This should NOT have value flow as with() returns a new array
}
29 changes: 29 additions & 0 deletions javascript/ql/test/library-tests/TripleDot/promise-try.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
async function t1() {
const promise = Promise.try(() => {
return source('try.1');
});
sink(await promise); // $ hasValueFlow=try.1
}

async function t2() {
const promise = Promise.try((x) => {
return x
}, source('try.2'));
sink(await promise); // $ hasValueFlow=try.2
}

async function t3() {
const promise = Promise.try((x) => {
throw x;
}, source('try.3'));
promise.catch(err => {
sink(err); // $ hasValueFlow=try.3
});
}

async function t4() {
const promise = Promise.try((x, y) => {
return y;
}, source('try.4.1'), source('try.4.2'));
sink(await promise); // $ hasValueFlow=try.4.2
}