Skip to content

Commit 6ad62e3

Browse files
committed
copyPropertyStep works interprocedurally
1 parent 06e898f commit 6ad62e3

4 files changed

Lines changed: 57 additions & 36 deletions

File tree

javascript/ql/src/semmle/javascript/Promises.qll

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,7 @@ private module ExceptionalPromiseFlow {
142142
this = promise
143143
}
144144

145-
override predicate store(DataFlow::Node pred, DataFlow::SourceNode succ, string prop) {
145+
override predicate store(DataFlow::Node pred, DataFlow::Node succ, string prop) {
146146
prop = rejectField() and
147147
(
148148
pred = promise.getRejectParameter().getACall().getArgument(0) or
@@ -185,14 +185,14 @@ private module ExceptionalPromiseFlow {
185185
succ = getCallback(1).getParameter(0)
186186
}
187187

188-
override predicate copyProperty(DataFlow::Node pred, DataFlow::SourceNode succ, string prop) {
188+
override predicate copyProperty(DataFlow::Node pred, DataFlow::Node succ, string prop) {
189189
not exists(this.getArgument(1)) and
190190
prop = rejectField() and
191191
pred = getReceiver() and
192192
succ = this
193193
}
194194

195-
override predicate store(DataFlow::Node pred, DataFlow::SourceNode succ, string prop) {
195+
override predicate store(DataFlow::Node pred, DataFlow::Node succ, string prop) {
196196
prop = rejectField() and
197197
pred = getCallback([0..1]).getExceptionalReturn() and
198198
succ = this
@@ -213,7 +213,7 @@ private module ExceptionalPromiseFlow {
213213
succ = getCallback(0).getParameter(0)
214214
}
215215

216-
override predicate store(DataFlow::Node pred, DataFlow::SourceNode succ, string prop) {
216+
override predicate store(DataFlow::Node pred, DataFlow::Node succ, string prop) {
217217
prop = rejectField() and
218218
pred = getCallback([0..1]).getExceptionalReturn() and
219219
succ = this
@@ -228,7 +228,7 @@ private module ExceptionalPromiseFlow {
228228
this.getMethodName() = "finally"
229229
}
230230

231-
override predicate copyProperty(DataFlow::Node pred, DataFlow::SourceNode succ, string prop) {
231+
override predicate copyProperty(DataFlow::Node pred, DataFlow::Node succ, string prop) {
232232
prop = rejectField() and
233233
pred = getReceiver() and
234234
succ = this

javascript/ql/src/semmle/javascript/dataflow/Configuration.qll

Lines changed: 41 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -226,20 +226,18 @@ abstract class Configuration extends string {
226226

227227
/**
228228
* Holds if the `pred` should be stored in the object `succ` under the property `prop`.
229-
*
230-
* `succ` is a DataFlow::SourceNode, as this is assumed by the `isAdditionalCopyPropertyStep` predicate.
231229
*/
232-
predicate isAdditionalStoreStep(DataFlow::Node pred, DataFlow::SourceNode succ, string prop) { none() }
230+
predicate isAdditionalStoreStep(DataFlow::Node pred, DataFlow::Node succ, string prop) { none() }
233231

234232
/**
235233
* Holds if the property `prop` of the object `pred` should be loaded into `succ`.
236234
*/
237235
predicate isAdditionalLoadStep(DataFlow::Node pred, DataFlow::Node succ, string prop) { none() }
238236

239237
/**
240-
* Holds if the property `prop` should be copied from the object `pred` to the object `succ`.
238+
* Holds if the property `prop` should be copied from the object `pred` to the object `succ`.
241239
*/
242-
predicate isAdditionalCopyPropertyStep(DataFlow::Node pred, DataFlow::SourceNode succ, string prop) { none() }
240+
predicate isAdditionalCopyPropertyStep(DataFlow::Node pred, DataFlow::Node succ, string prop) { none() }
243241
}
244242

245243
/**
@@ -470,11 +468,9 @@ abstract class AdditionalFlowStep extends DataFlow::Node {
470468

471469
/**
472470
* Holds if the `pred` should be stored in the object `succ` under the property `prop`.
473-
*
474-
* `succ` is a DataFlow::SourceNode, as this is assumed by the `copyProperty` predicate.
475471
*/
476472
cached
477-
predicate store(DataFlow::Node pred, DataFlow::SourceNode succ, string prop) { none() }
473+
predicate store(DataFlow::Node pred, DataFlow::Node succ, string prop) { none() }
478474

479475
/**
480476
* Holds if the property `prop` of the object `pred` should be loaded into `succ`.
@@ -486,7 +482,7 @@ abstract class AdditionalFlowStep extends DataFlow::Node {
486482
* Holds if the property `prop` should be copied from the object `pred` to the object `succ`.
487483
*/
488484
cached
489-
predicate copyProperty(DataFlow::Node pred, DataFlow::SourceNode succ, string prop) { none() }
485+
predicate copyProperty(DataFlow::Node pred, DataFlow::Node succ, string prop) { none() }
490486
}
491487

492488
/**
@@ -765,7 +761,7 @@ private predicate storeStep(
765761
returnedPropWrite(f, _, prop, mid)
766762
or
767763
exists(DataFlow::SourceNode base |
768-
isAdditionalStoreStep(mid, _, prop, cfg)
764+
isAdditionalStoreStep(mid, base, prop, cfg)
769765
and
770766
base.flowsToExpr(f.getAReturnedExpr())
771767
)
@@ -811,27 +807,31 @@ private predicate reachesReturn(
811807
)
812808
}
813809

810+
/**
811+
* Holds if the property `prop` of the object `pred` should be loaded into `succ`.
812+
*/
814813
private predicate isAdditionalLoadStep(DataFlow::Node pred, DataFlow::Node succ, string prop, DataFlow::Configuration cfg) {
815814
any(AdditionalFlowStep s).load(pred, succ, prop)
816815
or
817816
cfg.isAdditionalLoadStep(pred, succ, prop)
818817
}
819818

820-
private predicate isAdditionalStoreStep(DataFlow::Node pred, DataFlow::SourceNode succ, string prop, DataFlow::Configuration cfg) {
819+
/**
820+
* Holds if the `pred` should be stored in the object `succ` under the property `prop`.
821+
*/
822+
private predicate isAdditionalStoreStep(DataFlow::Node pred, DataFlow::Node succ, string prop, DataFlow::Configuration cfg) {
821823
any(AdditionalFlowStep s).store(pred, succ, prop)
822824
or
823825
cfg.isAdditionalStoreStep(pred, succ, prop)
824826
}
825827

826-
private predicate isAdditionalCopyPropertyStep(DataFlow::SourceNode pred, DataFlow::Node succ, string prop, DataFlow::Configuration cfg) {
827-
exists(DataFlow::Node predNode, DataFlow::SourceNode succNode |
828-
pred = predNode.getALocalSource() and
829-
succ.getALocalSource() = succNode
830-
|
831-
any(AdditionalFlowStep s).copyProperty(predNode, succNode, prop)
832-
or
833-
cfg.isAdditionalCopyPropertyStep(predNode, succNode, prop)
834-
)
828+
/**
829+
* Holds if the property `prop` should be copied from the object `pred` to the object `succ`.
830+
*/
831+
private predicate isAdditionalCopyPropertyStep(DataFlow::Node pred, DataFlow::Node succ, string prop, DataFlow::Configuration cfg) {
832+
any(AdditionalFlowStep s).copyProperty(pred, succ, prop)
833+
or
834+
cfg.isAdditionalCopyPropertyStep(pred, succ, prop)
835835
}
836836

837837
/**
@@ -869,7 +869,12 @@ private predicate reachableFromStoreBase(
869869
or
870870
exists(DataFlow::Node mid, PathSummary oldSummary, PathSummary newSummary |
871871
reachableFromStoreBase(prop, rhs, mid, cfg, oldSummary) and
872-
flowStep(mid, cfg, nd, newSummary) and
872+
(
873+
flowStep(mid, cfg, nd, newSummary)
874+
or
875+
existsCopyProperty(mid, nd, prop, cfg) and
876+
newSummary = PathSummary::level()
877+
) and
873878
summary = oldSummary.appendValuePreserving(newSummary)
874879
)
875880
}
@@ -885,28 +890,33 @@ pragma[noinline]
885890
private predicate flowThroughProperty(
886891
DataFlow::Node pred, DataFlow::Node succ, DataFlow::Configuration cfg, PathSummary summary
887892
) {
888-
exists(string prop, DataFlow::Node storeBase, DataFlow::Node loadBase, PathSummary oldSummary, PathSummary newSummary |
889-
reachableFromStoreBase(prop, pred, storeBase, cfg, oldSummary) and
890-
(storeBase = loadBase or existsCopyProperty(storeBase, loadBase, prop, cfg)) and
891-
loadStep(loadBase, succ, prop, cfg, newSummary) and
893+
exists(string prop, DataFlow::Node base, PathSummary oldSummary, PathSummary newSummary |
894+
reachableFromStoreBase(prop, pred, base, cfg, oldSummary) and
895+
loadStep(base, succ, prop, cfg, newSummary) and
892896
summary = oldSummary.append(newSummary)
893897
)
894898
}
895899

900+
/**
901+
* Holds if the property `prop` is copied from `fromNode` to `toNode`.
902+
*/
903+
bindingset[prop, cfg]
904+
private predicate existsCopyProperty(DataFlow::Node fromNode, DataFlow::Node toNode, string prop, DataFlow::Configuration cfg) {
905+
fromNode = toNode
906+
or
907+
existsCopyPropertyRecursive(fromNode, toNode, prop, cfg)
908+
}
909+
896910
/**
897911
* Holds if the property `prop` is copied from `fromNode` to `toNode` using at least 1 step.
898912
*
899913
* The recursion of this predicate has been unfolded once compared to a naive implementation in order to avoid having no constraint on `prop`.
900914
* Therefore a caller of this predicate should also test whether the `toNode` and `fromNode` are equal.
901915
*/
902-
private predicate existsCopyProperty(DataFlow::Node fromNode, DataFlow::Node toNode, string prop, DataFlow::Configuration cfg) {
916+
private predicate existsCopyPropertyRecursive(DataFlow::Node fromNode, DataFlow::Node toNode, string prop, DataFlow::Configuration cfg) {
903917
exists(DataFlow::Node mid |
904918
isAdditionalCopyPropertyStep(fromNode, mid, prop, cfg) and
905-
(
906-
existsCopyProperty(mid, toNode, prop, cfg)
907-
or
908-
mid = toNode
909-
)
919+
existsCopyProperty(mid, toNode, prop, cfg)
910920
)
911921
}
912922

javascript/ql/test/library-tests/Promises/flow.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,4 +69,9 @@
6969
} catch(e) {
7070
sink(e); // NOT OK!
7171
}
72+
73+
function chainedPromise() {
74+
return new Promise((resolve, reject) => reject(source)).then(() => {});
75+
}
76+
chainedPromise().then(() => {}).catch(e => sink(e)); // NOT OK!
7277
})();

javascript/ql/test/library-tests/Promises/tests.expected

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ test_PromiseDefinition_getExecutor
3333
| flow.js:55:11:55:58 | new Pro ... ource)) | flow.js:55:23:55:57 | (resolv ... source) |
3434
| flow.js:60:12:60:59 | new Pro ... ource)) | flow.js:60:24:60:58 | (resolv ... source) |
3535
| flow.js:65:9:65:56 | new Pro ... ource)) | flow.js:65:21:65:55 | (resolv ... source) |
36+
| flow.js:74:10:74:57 | new Pro ... ource)) | flow.js:74:22:74:56 | (resolv ... source) |
3637
| interflow.js:11:12:15:6 | new Pro ... \\n }) | interflow.js:11:24:15:5 | functio ... ;\\n } |
3738
| promises.js:3:17:5:4 | new Pro ... );\\n }) | promises.js:3:29:5:3 | functio ... e);\\n } |
3839
| promises.js:10:18:17:4 | new Pro ... );\\n }) | promises.js:10:30:17:3 | (res, r ... e);\\n } |
@@ -53,6 +54,7 @@ test_PromiseDefinition
5354
| flow.js:55:11:55:58 | new Pro ... ource)) |
5455
| flow.js:60:12:60:59 | new Pro ... ource)) |
5556
| flow.js:65:9:65:56 | new Pro ... ource)) |
57+
| flow.js:74:10:74:57 | new Pro ... ource)) |
5658
| interflow.js:11:12:15:6 | new Pro ... \\n }) |
5759
| promises.js:3:17:5:4 | new Pro ... );\\n }) |
5860
| promises.js:10:18:17:4 | new Pro ... );\\n }) |
@@ -65,6 +67,7 @@ test_PromiseDefinition_getAResolveHandler
6567
| flow.js:42:2:42:49 | new Pro ... ource)) | flow.js:42:56:42:64 | () => { } |
6668
| flow.js:55:11:55:58 | new Pro ... ource)) | flow.js:56:19:56:26 | () => {} |
6769
| flow.js:60:12:60:59 | new Pro ... ource)) | flow.js:61:21:61:28 | () => {} |
70+
| flow.js:74:10:74:57 | new Pro ... ource)) | flow.js:74:64:74:71 | () => {} |
6871
| promises.js:3:17:5:4 | new Pro ... );\\n }) | promises.js:6:16:8:3 | functio ... al;\\n } |
6972
| promises.js:10:18:17:4 | new Pro ... );\\n }) | promises.js:18:17:20:3 | (v) => ... v;\\n } |
7073
| promises.js:10:18:17:4 | new Pro ... );\\n }) | promises.js:26:20:28:3 | (v) => ... v;\\n } |
@@ -82,6 +85,7 @@ test_PromiseDefinition_getRejectParameter
8285
| flow.js:55:11:55:58 | new Pro ... ource)) | flow.js:55:33:55:38 | reject |
8386
| flow.js:60:12:60:59 | new Pro ... ource)) | flow.js:60:34:60:39 | reject |
8487
| flow.js:65:9:65:56 | new Pro ... ource)) | flow.js:65:31:65:36 | reject |
88+
| flow.js:74:10:74:57 | new Pro ... ource)) | flow.js:74:32:74:37 | reject |
8589
| interflow.js:11:12:15:6 | new Pro ... \\n }) | interflow.js:11:43:11:48 | reject |
8690
| promises.js:3:17:5:4 | new Pro ... );\\n }) | promises.js:3:48:3:53 | reject |
8791
| promises.js:10:18:17:4 | new Pro ... );\\n }) | promises.js:10:36:10:38 | rej |
@@ -99,6 +103,7 @@ test_PromiseDefinition_getResolveParameter
99103
| flow.js:55:11:55:58 | new Pro ... ource)) | flow.js:55:24:55:30 | resolve |
100104
| flow.js:60:12:60:59 | new Pro ... ource)) | flow.js:60:25:60:31 | resolve |
101105
| flow.js:65:9:65:56 | new Pro ... ource)) | flow.js:65:22:65:28 | resolve |
106+
| flow.js:74:10:74:57 | new Pro ... ource)) | flow.js:74:23:74:29 | resolve |
102107
| interflow.js:11:12:15:6 | new Pro ... \\n }) | interflow.js:11:34:11:40 | resolve |
103108
| promises.js:3:17:5:4 | new Pro ... );\\n }) | promises.js:3:39:3:45 | resolve |
104109
| promises.js:10:18:17:4 | new Pro ... );\\n }) | promises.js:10:31:10:33 | res |
@@ -126,4 +131,5 @@ flow
126131
| flow.js:2:15:2:22 | "source" | flow.js:58:24:58:24 | x |
127132
| flow.js:2:15:2:22 | "source" | flow.js:62:22:62:22 | x |
128133
| flow.js:2:15:2:22 | "source" | flow.js:70:8:70:8 | e |
134+
| flow.js:2:15:2:22 | "source" | flow.js:76:50:76:50 | e |
129135
| interflow.js:3:18:3:25 | "source" | interflow.js:18:10:18:14 | error |

0 commit comments

Comments
 (0)