Skip to content

Commit 9958432

Browse files
authored
Merge branch 'main' into main
2 parents 9086975 + 8ebbfb1 commit 9958432

File tree

8 files changed

+110
-18
lines changed

8 files changed

+110
-18
lines changed

cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/DataFlowImplSpecific.qll

+2
Original file line numberDiff line numberDiff line change
@@ -31,4 +31,6 @@ module CppDataFlow implements InputSig<Location> {
3131
predicate viableImplInCallContext = Private::viableImplInCallContext/2;
3232

3333
predicate neverSkipInPathGraph = Private::neverSkipInPathGraph/1;
34+
35+
int defaultFieldFlowBranchLimit() { result = 3 }
3436
}

cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/DataFlowPrivate.qll

+2-6
Original file line numberDiff line numberDiff line change
@@ -1652,8 +1652,6 @@ predicate validParameterAliasStep(Node node1, Node node2) {
16521652
)
16531653
}
16541654

1655-
private predicate isTopLevel(Cpp::Stmt s) { any(Function f).getBlock().getAStmt() = s }
1656-
16571655
private Cpp::Stmt getAChainedBranch(Cpp::IfStmt s) {
16581656
result = s.getThen()
16591657
or
@@ -1684,11 +1682,9 @@ private Instruction getAnInstruction(Node n) {
16841682
}
16851683

16861684
private newtype TDataFlowSecondLevelScope =
1687-
TTopLevelIfBranch(Cpp::Stmt s) {
1688-
exists(Cpp::IfStmt ifstmt | s = getAChainedBranch(ifstmt) and isTopLevel(ifstmt))
1689-
} or
1685+
TTopLevelIfBranch(Cpp::Stmt s) { s = getAChainedBranch(_) } or
16901686
TTopLevelSwitchCase(Cpp::SwitchCase s) {
1691-
exists(Cpp::SwitchStmt switchstmt | s = switchstmt.getASwitchCase() and isTopLevel(switchstmt))
1687+
exists(Cpp::SwitchStmt switchstmt | s = switchstmt.getASwitchCase())
16921688
}
16931689

16941690
/**

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

+4-4
Original file line numberDiff line numberDiff line change
@@ -850,10 +850,10 @@ module API {
850850
)
851851
or
852852
lbl = Label::promised() and
853-
PromiseFlow::storeStep(rhs, pred, Promises::valueProp())
853+
SharedTypeTrackingStep::storeStep(rhs, pred, Promises::valueProp())
854854
or
855855
lbl = Label::promisedError() and
856-
PromiseFlow::storeStep(rhs, pred, Promises::errorProp())
856+
SharedTypeTrackingStep::storeStep(rhs, pred, Promises::errorProp())
857857
or
858858
// The return-value of a getter G counts as a definition of property G
859859
// (Ordinary methods and properties are handled as PropWrite nodes)
@@ -1008,11 +1008,11 @@ module API {
10081008
propDesc = ""
10091009
)
10101010
or
1011-
PromiseFlow::loadStep(pred.getALocalUse(), ref, Promises::valueProp()) and
1011+
SharedTypeTrackingStep::loadStep(pred.getALocalUse(), ref, Promises::valueProp()) and
10121012
lbl = Label::promised() and
10131013
(propDesc = Promises::valueProp() or propDesc = "")
10141014
or
1015-
PromiseFlow::loadStep(pred.getALocalUse(), ref, Promises::errorProp()) and
1015+
SharedTypeTrackingStep::loadStep(pred.getALocalUse(), ref, Promises::errorProp()) and
10161016
lbl = Label::promisedError() and
10171017
(propDesc = Promises::errorProp() or propDesc = "")
10181018
}

javascript/ql/lib/semmle/javascript/internal/flow_summaries/Promises.qll

+49-3
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
private import javascript
66
private import semmle.javascript.dataflow.FlowSummary
7+
private import semmle.javascript.dataflow.TypeTracking
78
private import FlowSummaryUtil
89

910
DataFlow::SourceNode promiseConstructorRef() {
@@ -211,12 +212,57 @@ private class PromiseReject extends SummarizedCallable {
211212
}
212213
}
213214

215+
/**
216+
* A call to `Promise.all()`.
217+
*/
218+
class PromiseAllCall extends DataFlow::CallNode {
219+
PromiseAllCall() { this = promiseConstructorRef().getAMemberCall("all") }
220+
221+
/** Gets the source of the input array */
222+
DataFlow::ArrayCreationNode getInputArray() { result = this.getArgument(0).getALocalSource() }
223+
224+
/** Gets the `n`th element of the input array */
225+
DataFlow::Node getNthInput(int n) { result = this.getInputArray().getElement(n) }
226+
227+
/** Gets a reference to the output array. */
228+
DataFlow::SourceNode getOutputArray() {
229+
exists(AwaitExpr await |
230+
this.flowsToExpr(await.getOperand()) and
231+
result = await.flow()
232+
)
233+
or
234+
result = this.getAMethodCall("then").getCallback(0).getParameter(0)
235+
}
236+
237+
/** Gets the `n`th output */
238+
DataFlow::SourceNode getNthOutput(int n) {
239+
exists(string prop |
240+
result = this.getOutputArray().getAPropertyRead(prop) and
241+
n = prop.toInt()
242+
)
243+
}
244+
}
245+
246+
/**
247+
* Helps type-tracking simple uses of `Promise.all()` such as `const [a, b] = await Promise.all([x, y])`.
248+
*
249+
* Due to limited access path depth, type tracking can't track things that are in a promise and an array
250+
* at once. This generates a step directly from the input array to the output array.
251+
*/
252+
private class PromiseAllStep extends SharedTypeTrackingStep {
253+
override predicate loadStep(DataFlow::Node node1, DataFlow::Node node2, string prop) {
254+
exists(PromiseAllCall call, int n |
255+
node1 = call.getNthInput(n) and
256+
node2 = call.getNthOutput(n) and
257+
prop = Promises::valueProp()
258+
)
259+
}
260+
}
261+
214262
private class PromiseAll extends SummarizedCallable {
215263
PromiseAll() { this = "Promise.all()" }
216264

217-
override DataFlow::InvokeNode getACallSimple() {
218-
result = promiseConstructorRef().getAMemberCall("all")
219-
}
265+
override DataFlow::InvokeNode getACallSimple() { result instanceof PromiseAllCall }
220266

221267
override predicate propagatesFlow(string input, string output, boolean preservesValue) {
222268
preservesValue = true and
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
category: minorAnalysis
3+
---
4+
* Type information is now propagated more precisely through `Promise.all()` calls,
5+
leading to more resolved calls and more sources and sinks being detected.

misc/codegen/generators/qlgen.py

+10
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import logging
2525
import pathlib
2626
import re
27+
import shutil
2728
import subprocess
2829
import typing
2930
import itertools
@@ -257,6 +258,15 @@ def format(codeql, files):
257258
if not ql_files:
258259
return
259260
format_cmd = [codeql, "query", "format", "--in-place", "--"] + ql_files
261+
if "/" in codeql or "\\" in codeql:
262+
if not pathlib.Path(codeql).exists():
263+
raise FormatError(f"Provided CodeQL binary `{codeql}` does not exist")
264+
else:
265+
codeql_path = shutil.which(codeql)
266+
if not codeql_path:
267+
raise FormatError(
268+
f"`{codeql}` not found in PATH. Either install it, or pass `-- --codeql-binary` with a full path")
269+
codeql = codeql_path
260270
res = subprocess.run(format_cmd, stderr=subprocess.PIPE, text=True)
261271
if res.returncode:
262272
for line in res.stderr.splitlines():

misc/codegen/test/test_qlgen.py

+21-3
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,8 @@ def qlgen_opts(opts):
5252
opts.ql_format = True
5353
opts.root_dir = paths.root_dir
5454
opts.force = False
55+
opts.codeql_binary = "./my_fake_codeql"
56+
pathlib.Path(opts.codeql_binary).touch()
5557
return opts
5658

5759

@@ -499,7 +501,6 @@ def test_class_dir_imports(generate_import_list):
499501

500502

501503
def test_format(opts, generate, render_manager, run_mock):
502-
opts.codeql_binary = "my_fake_codeql"
503504
run_mock.return_value.stderr = "some\nlines\n"
504505
render_manager.written = [
505506
pathlib.Path("x", "foo.ql"),
@@ -508,13 +509,12 @@ def test_format(opts, generate, render_manager, run_mock):
508509
]
509510
generate([schema.Class('A')])
510511
assert run_mock.mock_calls == [
511-
mock.call(["my_fake_codeql", "query", "format", "--in-place", "--", "x/foo.ql", "bar.qll"],
512+
mock.call([opts.codeql_binary, "query", "format", "--in-place", "--", "x/foo.ql", "bar.qll"],
512513
stderr=subprocess.PIPE, text=True),
513514
]
514515

515516

516517
def test_format_error(opts, generate, render_manager, run_mock):
517-
opts.codeql_binary = "my_fake_codeql"
518518
run_mock.return_value.stderr = "some\nlines\n"
519519
run_mock.return_value.returncode = 1
520520
render_manager.written = [
@@ -526,6 +526,24 @@ def test_format_error(opts, generate, render_manager, run_mock):
526526
generate([schema.Class('A')])
527527

528528

529+
def test_format_no_codeql(opts, generate, render_manager, run_mock):
530+
pathlib.Path(opts.codeql_binary).unlink()
531+
render_manager.written = [
532+
pathlib.Path("bar.qll"),
533+
]
534+
with pytest.raises(qlgen.FormatError):
535+
generate([schema.Class('A')])
536+
537+
538+
def test_format_no_codeql_in_path(opts, generate, render_manager, run_mock):
539+
opts.codeql_binary = "my_fake_codeql"
540+
render_manager.written = [
541+
pathlib.Path("bar.qll"),
542+
]
543+
with pytest.raises(qlgen.FormatError):
544+
generate([schema.Class('A')])
545+
546+
529547
@pytest.mark.parametrize("force", [False, True])
530548
def test_manage_parameters(opts, generate, renderer, force):
531549
opts.force = force

python/ql/src/Resources/FileNotAlwaysClosedQuery.qll

+17-2
Original file line numberDiff line numberDiff line change
@@ -52,14 +52,29 @@ class FileWrapperCall extends DataFlow::CallCfgNode {
5252
abstract class FileClose extends DataFlow::CfgNode {
5353
/** Holds if this file close will occur if an exception is thrown at `raises`. */
5454
predicate guardsExceptions(DataFlow::CfgNode raises) {
55-
this.asCfgNode() = raises.asCfgNode().getAnExceptionalSuccessor().getASuccessor*()
55+
cfgGetASuccessorStar(raises.asCfgNode().getAnExceptionalSuccessor(), this.asCfgNode())
5656
or
5757
// The expression is after the close call.
5858
// This also covers the body of a `with` statement.
59-
raises.asCfgNode() = this.asCfgNode().getASuccessor*()
59+
cfgGetASuccessorStar(this.asCfgNode(), raises.asCfgNode())
6060
}
6161
}
6262

63+
private predicate cfgGetASuccessor(ControlFlowNode src, ControlFlowNode sink) {
64+
sink = src.getASuccessor()
65+
}
66+
67+
pragma[inline]
68+
private predicate cfgGetASuccessorPlus(ControlFlowNode src, ControlFlowNode sink) =
69+
fastTC(cfgGetASuccessor/2)(src, sink)
70+
71+
pragma[inline]
72+
private predicate cfgGetASuccessorStar(ControlFlowNode src, ControlFlowNode sink) {
73+
src = sink
74+
or
75+
cfgGetASuccessorPlus(src, sink)
76+
}
77+
6378
/** A call to the `.close()` method of a file object. */
6479
class FileCloseCall extends FileClose {
6580
FileCloseCall() { exists(DataFlow::MethodCallNode mc | mc.calls(this, "close")) }

0 commit comments

Comments
 (0)