Merge pull request #20375 from asgerf/js/promise-try

JS: Support Promise.try and Array.prototype.with
This commit is contained in:
Asger F
2025-09-16 14:44:07 +02:00
committed by GitHub
6 changed files with 110 additions and 0 deletions

View File

@@ -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" }

View File

@@ -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] }

View File

@@ -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 + "]"
)
or
input = "Argument[0].ReturnValue" and
output = "ReturnValue.Awaited"
or
input = "Argument[0].ReturnValue[exception]" and
output = "ReturnValue.Awaited[error]"
)
}
}

View File

@@ -0,0 +1,4 @@
---
category: minorAnalysis
---
* Data flow is now tracked through the `Promise.try` and `Array.prototype.with` functions.

View File

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

View File

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