Compare commits

..

331 Commits

Author SHA1 Message Date
Owen Mansel-Chan
a367294c23 Merge branch 'main' into copilot/automate-go-version-updates-again 2026-04-23 14:41:46 +01:00
Tom Hvitved
c64223ae56 Merge pull request #21748 from hvitved/shared/remove-deprecated
Shared: Remove deprecated code
2026-04-23 14:44:17 +02:00
Anders Schack-Mulligen
cb21044900 Merge pull request #21744 from aschackmull/csharp/ssa
C#: Replace BaseSSA classes with shared code.
2026-04-23 14:39:54 +02:00
Tom Hvitved
eee5b067b3 Merge pull request #21743 from hvitved/cfg/body-parts
C#: Move handling of callables into shared control flow library
2026-04-23 14:10:46 +02:00
Owen Mansel-Chan
bf960b8c76 Merge pull request #21652 from MarkLee131/fix/path-injection-torealpath
Java: recognize Path.toRealPath() as path normalization sanitizer
2026-04-23 11:18:23 +01:00
Owen Mansel-Chan
9f19791d8c Merge branch 'main' into fix/path-injection-torealpath 2026-04-23 10:40:47 +01:00
Tom Hvitved
61f1ef877f Swift: Remove deprecated references to deprecated shared code 2026-04-23 11:29:10 +02:00
Tom Hvitved
18da5f61cd Ruby: Remove deprecated references to deprecated shared code 2026-04-23 11:29:04 +02:00
Tom Hvitved
14dd72b3b1 C#: Remove deprecated references to deprecated shared code 2026-04-23 11:28:33 +02:00
Tom Hvitved
90ae086822 Shared: Remove deprecated code 2026-04-23 11:24:14 +02:00
Tom Hvitved
1a84b2b555 CFG: Use dense ranking 2026-04-23 11:22:38 +02:00
Tom Hvitved
71fa2166ee Apply suggestions from code review
Co-authored-by: Anders Schack-Mulligen <aschackmull@users.noreply.github.com>
2026-04-22 17:06:31 +02:00
Owen Mansel-Chan
d6abd4c72d Merge pull request #21745 from owen-mc/go/refactor-encryption-operation
Go: refactor `EncryptionOperation`
2026-04-22 15:46:49 +01:00
Owen Mansel-Chan
57eaed4dcc Refactor: remove fields from EncryptionOperation
Co-authored-by: Copilot <copilot@github.com>
2026-04-22 13:37:35 +01:00
Tom Hvitved
6ebf4ee394 Java: Adapt to changes in CFG library 2026-04-22 14:11:58 +02:00
Tom Hvitved
39cd86a48e C#: Move handling of callables into shared control flow library 2026-04-22 14:11:57 +02:00
Anders Schack-Mulligen
4b8e4b40af C#: Fix test. 2026-04-22 14:00:13 +02:00
Anders Schack-Mulligen
b0c31badc2 C#: Bugfix for multi-body baseSsa entry defs. 2026-04-22 11:53:44 +02:00
Anders Schack-Mulligen
ae7904f0c8 C#: Fix BaseSSA caching. 2026-04-22 11:53:44 +02:00
Anders Schack-Mulligen
bbd60031b1 C#: Replace references to old BaseSSA classes. 2026-04-22 11:53:40 +02:00
Anders Schack-Mulligen
145d3242a6 C#: Instantiate shared SSA wrappers for BaseSSA. 2026-04-22 11:51:44 +02:00
Michael Nebel
bca51a986c Merge pull request #21612 from michaelnebel/csharp/legacyasptaintedmember
C#: Taint members of types in ASP.NET user context.
2026-04-22 09:28:27 +02:00
Owen Mansel-Chan
62f15d0166 Merge pull request #21742 from owen-mc/docs/fixes
Docs: several minor fixes
2026-04-21 17:40:11 +01:00
Owen Mansel-Chan
b47afafe8e Fix duplicated quotation mark 2026-04-21 14:53:11 +01:00
Owen Mansel-Chan
3a13f77058 Fix typo "passd" -> "passed" 2026-04-21 14:52:48 +01:00
Owen Mansel-Chan
424b7decb1 Fix wrong parameter name 2026-04-21 14:52:22 +01:00
Owen Mansel-Chan
91f9f23138 Fix wrong function name 2026-04-21 14:52:10 +01:00
Anders Schack-Mulligen
f912731cd4 Merge pull request #21565 from aschackmull/csharp/cfg2
C#: Replace CFG with the shared implementation
2026-04-21 15:50:38 +02:00
Owen Mansel-Chan
6efb21314a Merge pull request #21523 from owen-mc/docs/mad/barriers
Document models-as-data barriers and barrier guards and add change notes
2026-04-21 13:49:19 +01:00
Owen Mansel-Chan
c91b5b3c2e Merge pull request #21650 from MarkLee131/fix/sensitive-log-fp-regex
Java: reduce false positives in sensitive-log
2026-04-21 13:48:32 +01:00
Michael Nebel
8b93ce2747 C#: Add ASP.NET test case for a collection type. 2026-04-21 14:27:06 +02:00
Michael Nebel
2d6197fd7d C#: Generalize ASP.NET taint members to collection types. 2026-04-21 14:27:03 +02:00
Michael Nebel
f826262f1d C#: Re-factor CollectionType into an abstract class and introduce getElementType predicate. 2026-04-21 14:26:59 +02:00
Michael Nebel
1055084305 C#: Address review comments. 2026-04-21 13:40:07 +02:00
Michael Nebel
dc0e7d4988 C#: Add change-note. 2026-04-21 13:40:04 +02:00
Michael Nebel
8060d2ff24 C#: Streamline the implementation for ASP.NET Core tainted members. 2026-04-21 13:40:02 +02:00
Michael Nebel
921d93e427 C#: Add an ASP.NET flow source example when using the WebMethod attribute. 2026-04-21 13:39:59 +02:00
Michael Nebel
dba1b7539f C#: Taint members of types used in ASP.NET remote flow source context. 2026-04-21 13:39:56 +02:00
Michael Nebel
77da545ab4 C#: Reclassify some sources as AspNetRemoteFlowSource. 2026-04-21 13:39:54 +02:00
Michael Nebel
0062eb1209 C#: Update remote flow sources test to also report tainted members. 2026-04-21 13:39:51 +02:00
Anders Schack-Mulligen
67c0515d3c Cfg: Undo consistency check change. 2026-04-21 13:10:03 +02:00
Michael B. Gale
58e9bad0a0 Merge pull request #21737 from github/post-release-prep/codeql-cli-2.25.3
Post-release preparation for codeql-cli-2.25.3
2026-04-21 11:48:30 +02:00
Anders Schack-Mulligen
a2a4e8288e C#: Deprecate ControlFlowElement.getAControlFlowNode and remove some splitting quantification. 2026-04-21 11:14:05 +02:00
Anders Schack-Mulligen
9de02b7ae6 Cfg: Use consistent casing in additional node tags. 2026-04-21 10:56:10 +02:00
Jeroen Ketema
7f2a13bc7a Merge pull request #21728 from jketema/jketema/swift-6.3.1
Swift: Update to Swift 6.3.1
2026-04-20 19:33:08 +02:00
Jeroen Ketema
abd08440a1 Swift: Update to Swift 6.3.1 2026-04-20 16:30:29 +02:00
Jeroen Ketema
d5ded932d3 Merge pull request #21723 from jketema/swift-fixed-array
Swift: Expose the generic arguments of `BuiltinFixedArrayType`s
2026-04-20 16:17:41 +02:00
Taus
b108e173a5 Merge pull request #21695 from github/tausbn/python-add-support-for-pep-798
Python: Add support for PEP-798
2026-04-20 15:01:01 +02:00
Anders Schack-Mulligen
b6f50f5992 C#: Simplify. 2026-04-20 14:43:28 +02:00
Anders Schack-Mulligen
3ceb96a45f C#: Eliminate Completion.qll. 2026-04-20 14:43:28 +02:00
Anders Schack-Mulligen
e928c224ae C#/Cfg: Some simple review fixes. 2026-04-20 14:43:27 +02:00
github-actions[bot]
a0bab539bb Post-release preparation for codeql-cli-2.25.3 2026-04-20 12:40:34 +00:00
Owen Mansel-Chan
9f310c20f3 Merge pull request #21734 from owen-mc/java/fix-partial-path-traversal
Java: fix bug in partial path traversal
2026-04-20 11:52:55 +01:00
Michael B. Gale
a73f7cb79d Merge pull request #21736 from github/release-prep/2.25.3
Release preparation for version 2.25.3
2026-04-20 12:29:07 +02:00
Michael B. Gale
abf374433b Merge changelog entries for cpp/implicit-function-declaration 2026-04-20 12:24:05 +02:00
Michael B. Gale
34b5dcfd5f Improve wording of actions note 2026-04-20 11:40:32 +02:00
github-actions[bot]
c861d99802 Release preparation for version 2.25.3 2026-04-20 09:27:23 +00:00
MarkLee131
92d205d1a8 Use set literal for getCommonSensitiveInfoFPRegex
Replace the five-way result = ... or result = ... disjunction with a
single equality on a set literal. Addresses the CodeQL style alert
"Use a set literal in place of or" reported by the self-scan on this
PR. Pure refactor, no semantic change.
2026-04-19 23:29:07 -04:00
Owen Mansel-Chan
c6f641eac4 Add change note
Co-authored-by: Copilot <copilot@github.com>
2026-04-19 07:18:48 +01:00
Owen Mansel-Chan
6d4a3974ce Fix bug so += File.separator is recognized 2026-04-19 07:18:42 +01:00
Owen Mansel-Chan
6099c5d034 Add SPURIOUS test for += File.separator 2026-04-19 07:18:00 +01:00
Owen Mansel-Chan
63d20a54d4 Use inline expectations with second test
Co-authored-by: Copilot <copilot@github.com>
2026-04-19 07:17:05 +01:00
Owen Mansel-Chan
dca7046d8c Make inline expectation comments specify query 2026-04-18 10:35:15 +01:00
Owen Mansel-Chan
2764580cdf Merge pull request #21718 from chmodxxx/java/woodstox-xxe
Java: Add XXE sink model for Woodstox WstxInputFactory
2026-04-17 17:25:15 +01:00
Salah Baddou
fb2d53e72a Address review: inline Woodstox into XmlParsers, move changelog to lib 2026-04-17 18:46:51 +04:00
Salah Baddou
f5131f9bc6 Java: Add XXE sink model for Woodstox WstxInputFactory
`com.ctc.wstx.stax.WstxInputFactory` overrides `createXMLStreamReader`,
`createXMLEventReader` and `setProperty` from `XMLInputFactory`, so the
existing `XmlInputFactory` model in `XmlParsers.qll` does not match calls
where the static receiver type is `WstxInputFactory` (or its supertype
`org.codehaus.stax2.XMLInputFactory2`). Woodstox is vulnerable to XXE in
its default configuration, so these missed sinks were false negatives in
`java/xxe`.

This adds a scoped framework model under
`semmle/code/java/frameworks/woodstox/WoodstoxXml.qll` (registered in the
`Frameworks` module of `XmlParsers.qll`) that recognises these calls as
XXE sinks and treats the factory as safe when both
`javax.xml.stream.supportDTD` and
`javax.xml.stream.isSupportingExternalEntities` are disabled — mirroring
the existing `XMLInputFactory` safe-configuration logic.
2026-04-17 18:46:51 +04:00
Taus
ac23e16786 Python: Move Python 3.15 data-flow tests to a separate file
We won't be able to run these tests until Python 3.15 is actually out
(and our CI is using it), so it seemed easiest to just put them in their
own test directory.
2026-04-17 13:16:46 +00:00
Owen Mansel-Chan
29b07d5d07 Merge pull request #21721 from owen-mc/go/remove-global-function-jump-step-from-local-flow
Go: Remove global function step from local flow
2026-04-17 14:09:16 +01:00
Tom Hvitved
14bdb62cf8 Merge pull request #21726 from hvitved/csharp/useless-to-string-fps
C#: Fix FPs in `RedundantToStringCall.ql`
2026-04-17 14:59:22 +02:00
Jeroen Ketema
3073c1c94c Merge pull request #21725 from github/jeongsoolee09/add-aligned-alloc-model
Add models of various `aligned_alloc`s
2026-04-17 14:31:25 +02:00
Owen Mansel-Chan
bc28e1726c Refactor to get rid of duplication 2026-04-17 13:24:16 +01:00
Taus
dc36609743 Python: Add data-flow tests
Alas, all these demonstrate is that we already don't fully support the
desugared `yield from` form.
2026-04-17 12:15:04 +00:00
Tom Hvitved
7bfdfbefa9 Add change note 2026-04-17 13:57:08 +02:00
Tom Hvitved
0235df8758 C#: Improve alert message for RedundantToStringCall.ql 2026-04-17 13:55:00 +02:00
Jeroen Ketema
e3b88cbad3 Swift: Fix change note 2026-04-17 13:29:24 +02:00
Jeroen Ketema
dd2440086f Swift: Add change note 2026-04-17 13:24:17 +02:00
Jeongsoo Lee
abec00cd34 Update cpp/ql/src/change-notes/2026-04-16-add-model-for-aligned-alloc.md
Co-authored-by: Jeroen Ketema <93738568+jketema@users.noreply.github.com>
2026-04-17 07:08:38 -04:00
Owen Mansel-Chan
9f4fd7fab0 Remove a data flow consistency exclusion
This is no longer needed.
2026-04-17 11:27:36 +01:00
Paolo Tranquilli
5342cc79fb Merge pull request #21574 from github/redsun82/actions/remove-harden-runner-false-positive
Remove false positive injection sink models for `docker/build-push-action` and `step-security/harden-runner`
2026-04-17 09:43:45 +02:00
Tom Hvitved
426962e348 C#: Fix FPs in RedundantToStringCall.ql 2026-04-17 09:37:19 +02:00
Tom Hvitved
33e9c02079 C#: Add more tests for RedundantToStringCall.ql 2026-04-17 09:33:13 +02:00
jeongsoolee09
553ed103c3 Add a change note 2026-04-16 21:31:55 -04:00
jeongsoolee09
d2d594a8ff Add models of ::aligned_alloc, std::aligned_alloc, and bsl::aligned_alloc 2026-04-16 21:21:09 -04:00
Taus
6c675fcede Python: Consolidate duplicated code 2026-04-16 21:14:42 +00:00
Jeroen Ketema
efddfab564 Swift: Expose the generic arguments of BuiltinFixedArrays 2026-04-16 17:07:20 +02:00
Owen Mansel-Chan
73cc54c10d Use monospace instead of bold for quoted code 2026-04-16 12:35:38 +01:00
Owen Mansel-Chan
69c150d5f6 Use monospace instead of bold for predicate signatures 2026-04-16 12:34:47 +01:00
Owen Mansel-Chan
82d9d46fde Remove duplication and standardize wording
Co-authored-by: Copilot <copilot@github.com>
2026-04-16 12:26:44 +01:00
Owen Mansel-Chan
5a7b1b91e0 Fix mistakes in explanation of override column
To avoid copy-paste mistakes and make them more consistent we just use
the word "model".
2026-04-16 11:41:30 +01:00
Owen Mansel-Chan
2c16cb46ad Quote library name in backticks
Co-authored-by: Sarita Iyer <66540150+saritai@users.noreply.github.com>
2026-04-16 11:30:10 +01:00
Owen Mansel-Chan
f6135b70ea Remove global function step from local flow 2026-04-16 11:15:01 +01:00
Tom Hvitved
ee34e3353d Merge pull request #21698 from hvitved/rust/type-inference-index-expr
Rust: Replace special handling of index expressions in type inference
2026-04-16 09:03:06 +02:00
Jon Janego
f95ee129df Merge pull request #21713 from github/codeql-spark-run-24459914636
Update changelog documentation site for codeql-cli-2.25.2
2026-04-15 09:55:53 -05:00
github-actions[bot]
d24fb29ff4 update codeql documentation 2026-04-15 14:23:47 +00:00
Jeroen Ketema
97d8993fc5 Merge pull request #21667 from jketema/jketema/swift-6.3
Swift: Update to Swift 6.3
2026-04-15 14:07:23 +02:00
Jeroen Ketema
7d1c62daa6 Swift: Address review comment 2026-04-15 13:37:15 +02:00
Tom Hvitved
597d81038a Merge pull request #21708 from github/copilot/fix-missed-opportunity-to-use-select
Fix false positive in `MissedSelectOpportunity` when foreach body uses `await`
2026-04-15 11:32:02 +02:00
Tom Hvitved
069431941e Merge pull request #21596 from hvitved/rust/data-flow-closure-type
Rust: Track closure types in data flow
2026-04-15 10:32:05 +02:00
Tom Hvitved
609621f638 Merge pull request #21679 from hvitved/rust/type-inference-forall-checks
Rust: Replace recursion through `forall` with ranked recursion
2026-04-15 09:43:37 +02:00
Jeroen Ketema
ae2226345e Merge pull request #21709 from jketema/depr
C++: Remove deprecated code deprecated more than a year ago
2026-04-14 17:04:48 +02:00
Owen Mansel-Chan
f79ffe792e Fix docs: "branch" -> "acceptingValue" 2026-04-14 15:41:02 +01:00
Owen Mansel-Chan
87f2e21ae9 Fix docs: "acceptingvalue" -> "acceptingValue" 2026-04-14 15:37:17 +01:00
Owen Mansel-Chan
6321482a46 Remove mention of extension ID 2026-04-14 15:29:52 +01:00
Owen Mansel-Chan
8081d4602b Use hyphens in column names: "access-path", "accepting-value" 2026-04-14 15:27:42 +01:00
Owen Mansel-Chan
2ecf086333 Include parameters when quoting extensible predicate name 2026-04-14 15:27:41 +01:00
Owen Mansel-Chan
76d165e71e "modelling" -> "modeling" in docs 2026-04-14 15:27:39 +01:00
Owen Mansel-Chan
8f17b73796 Fix link formatting in change notes 2026-04-14 15:27:37 +01:00
Owen Mansel-Chan
6d4e8bfcb2 Correct extensible predicate signatures in docs
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-04-14 15:27:35 +01:00
Owen Mansel-Chan
a2a0c087e1 Remove incorrect parameter of extensible predicate
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-04-14 15:27:33 +01:00
Owen Mansel-Chan
c86ba38a4e Add change notes 2026-04-14 15:27:31 +01:00
Owen Mansel-Chan
415330d5eb Update docs for barriers and barrier guards 2026-04-14 15:27:29 +01:00
Owen Mansel-Chan
05e3073165 List extensible predicates for barriers and barrier guards 2026-04-14 15:27:27 +01:00
Owen Mansel-Chan
ef9136c053 (Formatting) Remove erroneous bullet point in ruby docs 2026-04-14 15:27:25 +01:00
Owen Mansel-Chan
f02ccd36cc (Trivial) Remove trailing spaces in some docs 2026-04-14 15:27:21 +01:00
Owen Mansel-Chan
6e0bee7471 Merge pull request #21691 from github/dependabot/go_modules/go/extractor/extractor-dependencies-2d1b0e128d
Bump the extractor-dependencies group across 1 directory with 2 updates
2026-04-14 15:26:00 +01:00
Henry Mercer
cb1fd76a4c Merge pull request #21658 from github/post-release-prep/codeql-cli-2.25.2
Post-release preparation for codeql-cli-2.25.2
2026-04-14 15:24:13 +01:00
Tom Hvitved
467933bbb1 Rust: Also add specialized IndexMut implementations 2026-04-14 15:45:14 +02:00
Henry Mercer
43c9b95e6f Merge branch 'main' into post-release-prep/codeql-cli-2.25.2 2026-04-14 13:56:52 +01:00
Tom Hvitved
878cfd720c C#: Use inline test expectations 2026-04-14 14:41:28 +02:00
Geoffrey White
666c8bf87a Merge pull request #21635 from geoffw0/suspicioussizeof2
C++: Upgrade cpp/suspicious-add-sizeof to high precision
2026-04-14 13:04:24 +01:00
Jeroen Ketema
07b02942db Merge remote-tracking branch 'upstream/main' into jketema/swift-6.3 2026-04-14 13:54:16 +02:00
Jeroen Ketema
9ef088d423 C++: Add change note 2026-04-14 13:46:43 +02:00
Taus
8b1ecf05c9 Python: Update test output
This change reflects the `(value, key)` to `(key, value)` fix in an
earlier commit.
2026-04-14 13:27:31 +02:00
Taus
15790aa00c Python: Add change note 2026-04-14 13:27:31 +02:00
Taus
de900fc3b5 Python: Add QL test for comprehensions with unpacking 2026-04-14 13:27:31 +02:00
Taus
fc5b3562c3 Python: Add parser test for comprehensions with unpacking 2026-04-14 13:27:31 +02:00
Taus
90b64616f7 Python: Also fix (value, key) bug in old parser 2026-04-14 13:27:31 +02:00
Taus
91d4cf6624 Python: Update python.tsg
First, we extend the various location overriding hacks to also accept
list and dict splats in various places. Having done this, we then have
to tackle how to actually desugar these new comprehension forms (as this
is what we currently do for the old forms).

As a reminder, a list comprehension like `[x for x in y]` currently gets
desugared into a small local function, something like

```python
def listcomp(a):
    for x in a:
        yield x
listcomp(y)
```

For `[*x for x in y]`, the behaviour we want is that we unpack `x`
before yielding its elements in turn. This is essentially what we would
get if we were to use `yield from x` instead of `yield x` in the above
desugaring, so that's what we do. This also works for set
comprehensions.

For dict comprehensions, it's slightly more complicated. Here, the
generator function instead yields a stream of `(key, value)` tuples.
(And apparently the old parser got this wrong and emitted `(value, key)`
pairs instead, which we faithfully recreated in the new parser as well.
We fix that bug in both parsers while we're at it). So, a bare `yield
from` is not enough, we also need a `.items()` call to get the
double-starred expression to emit its items as a stream of tuples (that
we then `yield from`.

To make this (hopefully) less verbose in the implementation, we defer
the decision of whether to use `yield` or `yield from` by introducing a
`yield_kind` scoped variable that determines the type of the actual AST
node. And of course for dict comprehensions with unpacking we need to
synthesise the extra machinery mentioned above.

On the plus side, this means we don't have to mess with control-flow, as
the existing machinery should be able to handle the desugared syntax
just fine.
2026-04-14 13:27:31 +02:00
Taus
97086c3cc9 Python: Regenerate parser files 2026-04-14 13:27:31 +02:00
Taus
4b5ff0b89e Python: Support unpacking in comprehensions in tree-sitter-python
This is the easy part -- we just allow `dictionary_splat` or
`list_splat` to appear in the same place as the expression.
2026-04-14 13:27:31 +02:00
Taus
c748fdf8ee Merge pull request #21694 from github/tausbn/python-add-support-for-pep-810
Python: Add support for PEP 810
2026-04-14 13:27:08 +02:00
Tom Hvitved
b749ad645a Merge pull request #21706 from hvitved/rust/type-inference-perf-fixes
Rust: Improve performance of two type inference predicates
2026-04-14 13:06:26 +02:00
Jeroen Ketema
12868e5140 C++: Remove deprecated code added more than a year ago 2026-04-14 13:03:10 +02:00
Geoffrey White
fe7e8480b2 Merge branch 'main' into suspicioussizeof2 2026-04-14 10:52:00 +01:00
Anders Schack-Mulligen
e0952948ba Merge pull request #21701 from aschackmull/csharp/intvalue
C#: Introduce Expr.getIntValue.
2026-04-14 11:23:29 +02:00
Owen Mansel-Chan
7458674470 Merge pull request #21584 from owen-mc/shared/update-mad-comments
Shared: update code comments explaining models-as-data format to include barriers and barrier guards
2026-04-14 09:30:28 +01:00
copilot-swe-agent[bot]
3483050526 Fix false positive in MissedSelectOpportunity for async/await loops
Agent-Logs-Url: https://github.com/github/codeql/sessions/3e8f4320-2bf4-45f5-b9ea-dad41d522d84

Co-authored-by: hvitved <3667920+hvitved@users.noreply.github.com>
2026-04-14 08:18:02 +00:00
copilot-swe-agent[bot]
0e66555e37 Initial plan 2026-04-14 08:10:53 +00:00
Jeroen Ketema
0724c22f28 Merge pull request #21702 from jketema/conv-string
C++: Use new `getConvSpecString` instead of `getConvSpecOffset` and `substring`
2026-04-14 10:00:51 +02:00
Tom Hvitved
d69be77035 Rust: Avoid expensive regex calls
Before
```
Pipeline standard for TypeInference::AssocFunctionResolution::AssocFunctionCall.hasIncompatibleTarget/5#85c07422@d5eb7r0w was evaluated in 782 iterations totaling 13208ms (delta sizes total: 20187834).
            1464   ~2%    {7} r1 = JOIN `TypeInference::AssocFunctionResolution::SelfArgIsInstantiationOf::argIsInstantiationOf/6#aaa87ac9#prev_delta` WITH `TypeInference::AssocFunctionResolution::OverloadedCallArgsAreInstantiationsOf::argsAreNotInstantiationsOf/2#6a6070f7#prev` ON FIRST 2 OUTPUT Lhs.5, _, Lhs.0, Lhs.1, Lhs.2, Lhs.3, Lhs.4
            1464   ~0%    {7}    | REWRITE WITH Out.1 := ""
            1464   ~0%    {6}    | JOIN WITH `FunctionType::AssocFunctionType.getTypeAt/1#dispred#d4d46f61` ON FIRST 2 OUTPUT Lhs.2, Lhs.3, Lhs.4, Lhs.5, Lhs.6, Rhs.2

          173691   ~1%    {7} r2 = JOIN `TypeInference::AssocFunctionResolution::OverloadedCallArgsAreInstantiationsOf::argsAreNotInstantiationsOf/2#6a6070f7#prev_delta` WITH `TypeInference::AssocFunctionResolution::SelfArgIsInstantiationOf::argIsInstantiationOf/6#aaa87ac9#prev` ON FIRST 2 OUTPUT Rhs.5, _, Lhs.0, Lhs.1, Rhs.2, Rhs.3, Rhs.4
          173691   ~1%    {7}    | REWRITE WITH Out.1 := ""
          173691   ~1%    {6}    | JOIN WITH `FunctionType::AssocFunctionType.getTypeAt/1#dispred#d4d46f61` ON FIRST 2 OUTPUT Lhs.2, Lhs.3, Lhs.4, Lhs.5, Lhs.6, Rhs.2

        20022454   ~0%    {7} r3 = SCAN `TypeInference::AssocFunctionResolution::SelfArgIsInstantiationOf::argIsNotInstantiationOf/6#1b8e512e#prev_delta` OUTPUT In.0, In.1, In.2, In.3, In.4, In.5, _
        20022454   ~0%    {7}    | REWRITE WITH Out.6 := "^([0-9]+)\\..*$"
        20022175   ~2%    {9}    | JOIN WITH PRIMITIVE regexpCapture#bbff ON Lhs.5,Lhs.6
        20022175   ~2%    {10}    | SCAN OUTPUT In.0, In.1, In.2, In.3, In.4, In.5, In.6, In.7, In.8, _
                          {9}    | REWRITE WITH Tmp.9 := 1, TEST InOut.7 = Tmp.9 KEEPING 9
        20022175   ~1%    {7}    | SCAN OUTPUT In.8, In.0, In.1, In.2, In.3, In.4, In.5
        20022175   ~1%    {8}    | JOIN WITH `UnboundList::Make<Locations::Location,TypeInference::M1::UnboundListInput>::encode/1#47b2ec3f_10#join_rhs` ON FIRST 1 OUTPUT Rhs.1, Lhs.1, Lhs.2, Lhs.3, Lhs.4, Lhs.5, Lhs.6, Lhs.0
        20022175   ~0%    {10}    | JOIN WITH `Type::Type.getATypeParameter/0#dispred#ddf0e8ff_10#join_rhs` ON FIRST 1 OUTPUT Lhs.1, Lhs.2, Lhs.3, Lhs.4, Lhs.5, Rhs.1, _, Lhs.6, Lhs.7, _
                          {7}    | REWRITE WITH Tmp.6 := length(In.8), Tmp.9 := 1, Tmp.6 := (Tmp.6 + Tmp.9), Out.6 := suffix(In.7,Tmp.6) KEEPING 7
        20022175   ~0%    {6}    | SCAN OUTPUT In.0, In.1, In.2, In.3, In.4, In.5

        20197330   ~0%    {6} r4 = r1 UNION r2 UNION r3
        20187834   ~0%    {6}    | AND NOT `TypeInference::AssocFunctionResolution::AssocFunctionCall.hasIncompatibleTarget/5#85c07422#prev`(FIRST 6)
                          return r4
```

After
```
Pipeline standard for TypeInference::AssocFunctionResolution::AssocFunctionCall.hasIncompatibleTarget/5#85c07422@a58ce91w was evaluated in 537 iterations totaling 382ms (delta sizes total: 20033950).
        19862347   ~0%    {7} r1 = SCAN `TypeInference::AssocFunctionResolution::SelfArgIsInstantiationOf::argIsNotInstantiationOf/6#1b8e512e#prev_delta` OUTPUT In.5, _, In.0, In.1, In.2, In.3, In.4
        19862347   ~0%    {7}    | REWRITE WITH Out.1 := ""

          174684   ~1%    {7} r2 = SCAN `TypeInference::AssocFunctionResolution::AssocFunctionCall.hasIncompatibleArgsTarget/5#dispred#7d49b9f9#prev_delta` OUTPUT In.5, _, In.0, In.1, In.2, In.3, In.4
          174684   ~1%    {7}    | REWRITE WITH Out.1 := ""

        20037031   ~0%    {7} r3 = r1 UNION r2
        20037031   ~0%    {6}    | JOIN WITH `FunctionType::AssocFunctionType.getTypeAt/1#dispred#d4d46f61` ON FIRST 2 OUTPUT Lhs.2, Lhs.3, Lhs.4, Lhs.5, Lhs.6, Rhs.2
        20033950   ~0%    {6}    | AND NOT `TypeInference::AssocFunctionResolution::AssocFunctionCall.hasIncompatibleTarget/5#85c07422#prev`(FIRST 6)
                          return r3
```
2026-04-14 09:51:45 +02:00
Tom Hvitved
0db62b2e68 Type inference: Fix bad join
Before
```
Pipeline standard for TypeInference::M2::SatisfiesConstraintWithTypeMatching<TypeInference::FunctionCallMatching::AccessConstraint::RelevantAccess,TypeMention::TypeMention,TypeInference::FunctionCallMatching::AccessConstraint::SatisfiesTypeParameterConstraintInput>::TermIsInstantiationOfCondition::typeParametersHaveEqualInstantiationToIndex/4#dde26112@d5eb7x9q was evaluated in 471 iterations totaling 24306ms (delta sizes total: 42097188).
        5676156578   ~1%    {7} r1 = JOIN `TypeInference::M2::SatisfiesConstraintWithTypeMatching<TypeInference::FunctionCallMatching::AccessConstraint::RelevantAccess,TypeMention::TypeMention,TypeInference::FunctionCallMatching::AccessConstraint::SatisfiesTypeParameterConstraintInput>::TermIsInstantiationOfCondition::typeParametersHaveEqualInstantiationToIndex/4#dde26112#prev_delta` WITH `TypeInference::M2::SatisfiesConstraintWithTypeMatching<TypeInference::FunctionCallMatching::AccessConstraint::RelevantAccess,TypeMention::TypeMention,TypeInference::FunctionCallMatching::AccessConstraint::SatisfiesTypeParameterConstraintInput>::TermIsInstantiationOfCondition::typeParametersEqual/4#a276e5d4#prev` ON FIRST 3 OUTPUT Lhs.0, Lhs.1, Lhs.2, Rhs.3, _, Lhs.3, _
                            {5}    | REWRITE WITH Tmp.4 := 1, Out.4 := (Tmp.4 + In.5), Tmp.6 := 0, TEST Out.4 != Tmp.6 KEEPING 5
        5676156578   ~1%    {5}    | SCAN OUTPUT In.1, In.4, In.3, In.0, In.2
          41691564   ~1%    {4}    | JOIN WITH `TypeInference::M2::SatisfiesConstraintWithTypeMatching<TypeInference::AssocFunctionResolution::ArgSatisfiesBlanketLikeConstraint::ArgumentTypeAndBlanketOffset,TypeMention::TypeMention,TypeInference::AssocFunctionResolution::ArgSatisfiesBlanketLikeConstraint::SatisfiesBlanketConstraint::Inp>::TermIsInstantiationOfCondition::getNthTypeParameter/2#40c66343` ON FIRST 3 OUTPUT Lhs.3, Lhs.0, Lhs.4, Lhs.1

          42097188   ~2%    {4} r2 = SCAN `TypeInference::M2::SatisfiesConstraintWithTypeMatching<TypeInference::FunctionCallMatching::AccessConstraint::RelevantAccess,TypeMention::TypeMention,TypeInference::FunctionCallMatching::AccessConstraint::SatisfiesTypeParameterConstraintInput>::TermIsInstantiationOfCondition::typeParametersEqual/4#a276e5d4#prev_delta` OUTPUT In.1, In.3, In.0, In.2

          42097188   ~1%    {5} r3 = JOIN r2 WITH `TypeInference::M2::SatisfiesConstraintWithTypeMatching<TypeInference::AssocFunctionResolution::ArgSatisfiesBlanketLikeConstraint::ArgumentTypeAndBlanketOffset,TypeMention::TypeMention,TypeInference::AssocFunctionResolution::ArgSatisfiesBlanketLikeConstraint::SatisfiesBlanketConstraint::Inp>::TermIsInstantiationOfCondition::getNthTypeParameter/2#40c66343_021#join_rhs` ON FIRST 2 OUTPUT Lhs.2, Lhs.0, Lhs.3, Rhs.2, _
                            {4}    | REWRITE WITH Tmp.4 := 0, TEST InOut.3 != Tmp.4 KEEPING 4
          41691564   ~1%    {5}    | SCAN OUTPUT In.0, In.1, In.2, _, In.3
          41691564   ~1%    {5}    | REWRITE WITH Tmp.3 := 1, Out.3 := (InOut.4 - Tmp.3)
                 0   ~0%    {4}    | JOIN WITH `TypeInference::M2::SatisfiesConstraintWithTypeMatching<TypeInference::FunctionCallMatching::AccessConstraint::RelevantAccess,TypeMention::TypeMention,TypeInference::FunctionCallMatching::AccessConstraint::SatisfiesTypeParameterConstraintInput>::TermIsInstantiationOfCondition::typeParametersHaveEqualInstantiationToIndex/4#dde26112#prev` ON FIRST 4 OUTPUT Lhs.0, Lhs.1, Lhs.2, Lhs.4

          42097188   ~0%    {6} r4 = JOIN r2 WITH `TypeInference::M2::SatisfiesConstraintWithTypeMatching<TypeInference::AssocFunctionResolution::ArgSatisfiesBlanketLikeConstraint::ArgumentTypeAndBlanketOffset,TypeMention::TypeMention,TypeInference::AssocFunctionResolution::ArgSatisfiesBlanketLikeConstraint::SatisfiesBlanketConstraint::Inp>::TermIsInstantiationOfCondition::getNthTypeParameter/2#40c66343_021#join_rhs` ON FIRST 2 OUTPUT Lhs.2, Lhs.0, Lhs.3, Lhs.1, Rhs.2, _
                            {5}    | REWRITE WITH Tmp.5 := 0, TEST InOut.4 = Tmp.5 KEEPING 5
            405624   ~1%    {5}    | SCAN OUTPUT In.1, _, In.3, In.0, In.2
            405624   ~1%    {5}    | REWRITE WITH Out.1 := 0
            405624   ~0%    {4}    | JOIN WITH `TypeInference::M2::SatisfiesConstraintWithTypeMatching<TypeInference::AssocFunctionResolution::ArgSatisfiesBlanketLikeConstraint::ArgumentTypeAndBlanketOffset,TypeMention::TypeMention,TypeInference::AssocFunctionResolution::ArgSatisfiesBlanketLikeConstraint::SatisfiesBlanketConstraint::Inp>::TermIsInstantiationOfCondition::getNthTypeParameter/2#40c66343` ON FIRST 3 OUTPUT Lhs.3, Lhs.0, Lhs.4, _
            405624   ~1%    {4}    | REWRITE WITH Out.3 := 0

          42097188   ~1%    {4} r5 = r1 UNION r3 UNION r4
          42097188   ~1%    {4}    | AND NOT `TypeInference::M2::SatisfiesConstraintWithTypeMatching<TypeInference::FunctionCallMatching::AccessConstraint::RelevantAccess,TypeMention::TypeMention,TypeInference::FunctionCallMatching::AccessConstraint::SatisfiesTypeParameterConstraintInput>::TermIsInstantiationOfCondition::typeParametersHaveEqualInstantiationToIndex/4#dde26112#prev`(FIRST 4)
                            return r5
```

After
```
Pipeline standard for TypeInference::M2::SatisfiesConstraintWithTypeMatching<TypeInference::FunctionCallMatching::AccessConstraint::RelevantAccess,TypeMention::TypeMention,TypeInference::FunctionCallMatching::AccessConstraint::SatisfiesTypeParameterConstraintInput>::TermIsInstantiationOfCondition::typeParametersHaveEqualInstantiationToIndex/4#dde26112@96df1x2u was evaluated in 471 iterations totaling 4058ms (delta sizes total: 42097188).
        42097188   ~0%    {4} r1 = SCAN `TypeInference::M2::SatisfiesConstraintWithTypeMatching<TypeInference::FunctionCallMatching::AccessConstraint::RelevantAccess,TypeMention::TypeMention,TypeInference::FunctionCallMatching::AccessConstraint::SatisfiesTypeParameterConstraintInput>::TermIsInstantiationOfCondition::typeParametersEqual/5#ddfcf430#prev_delta` OUTPUT In.3, In.0, In.1, In.2
          405624   ~0%    {4}    | JOIN WITH const_0 ON FIRST 1 OUTPUT Lhs.1, Lhs.2, Lhs.3, _
          405624   ~1%    {4}    | REWRITE WITH Out.3 := 0

        42097188   ~1%    {6} r2 = SCAN `TypeInference::M2::SatisfiesConstraintWithTypeMatching<TypeInference::FunctionCallMatching::AccessConstraint::RelevantAccess,TypeMention::TypeMention,TypeInference::FunctionCallMatching::AccessConstraint::SatisfiesTypeParameterConstraintInput>::TermIsInstantiationOfCondition::typeParametersHaveEqualInstantiationToIndex/4#dde26112#prev_delta` OUTPUT In.0, In.1, In.2, _, In.3, _
        42097188   ~1%    {4}    | REWRITE WITH Tmp.3 := 1, Out.3 := (Tmp.3 + In.4), Tmp.5 := 0, TEST Out.3 != Tmp.5 KEEPING 4
        41691564   ~1%    {4}    | JOIN WITH `TypeInference::M2::SatisfiesConstraintWithTypeMatching<TypeInference::FunctionCallMatching::AccessConstraint::RelevantAccess,TypeMention::TypeMention,TypeInference::FunctionCallMatching::AccessConstraint::SatisfiesTypeParameterConstraintInput>::TermIsInstantiationOfCondition::typeParametersEqual/5#ddfcf430#prev` ON FIRST 4 OUTPUT Lhs.0, Lhs.1, Lhs.2, Lhs.3

        42097188   ~1%    {6} r3 = SCAN `TypeInference::M2::SatisfiesConstraintWithTypeMatching<TypeInference::FunctionCallMatching::AccessConstraint::RelevantAccess,TypeMention::TypeMention,TypeInference::FunctionCallMatching::AccessConstraint::SatisfiesTypeParameterConstraintInput>::TermIsInstantiationOfCondition::typeParametersEqual/5#ddfcf430#prev_delta` OUTPUT In.0, In.1, In.2, In.3, In.4, _
                          {5}    | REWRITE WITH Tmp.5 := 0, TEST InOut.3 != Tmp.5 KEEPING 5
        41691564   ~1%    {5}    | SCAN OUTPUT In.0, In.1, In.2, _, In.3
        41691564   ~1%    {5}    | REWRITE WITH Tmp.3 := 1, Out.3 := (InOut.4 - Tmp.3)
               0   ~0%    {4}    | JOIN WITH `TypeInference::M2::SatisfiesConstraintWithTypeMatching<TypeInference::FunctionCallMatching::AccessConstraint::RelevantAccess,TypeMention::TypeMention,TypeInference::FunctionCallMatching::AccessConstraint::SatisfiesTypeParameterConstraintInput>::TermIsInstantiationOfCondition::typeParametersHaveEqualInstantiationToIndex/4#dde26112#prev` ON FIRST 4 OUTPUT Lhs.0, Lhs.1, Lhs.2, Lhs.4

        42097188   ~1%    {4} r4 = r1 UNION r2 UNION r3
        42097188   ~1%    {4}    | AND NOT `TypeInference::M2::SatisfiesConstraintWithTypeMatching<TypeInference::FunctionCallMatching::AccessConstraint::RelevantAccess,TypeMention::TypeMention,TypeInference::FunctionCallMatching::AccessConstraint::SatisfiesTypeParameterConstraintInput>::TermIsInstantiationOfCondition::typeParametersHaveEqualInstantiationToIndex/4#dde26112#prev`(FIRST 4)
                          return r4
```
2026-04-14 09:34:13 +02:00
Jeroen Ketema
26715fc95c C++: Rename rst to convSpec 2026-04-14 08:03:51 +02:00
dependabot[bot]
b19f2c6874 Bump the extractor-dependencies group in /go/extractor with 2 updates
Bumps the extractor-dependencies group in /go/extractor with 2 updates: [golang.org/x/mod](https://github.com/golang/mod) and [golang.org/x/tools](https://github.com/golang/tools).


Updates `golang.org/x/mod` from 0.34.0 to 0.35.0
- [Commits](https://github.com/golang/mod/compare/v0.34.0...v0.35.0)

Updates `golang.org/x/tools` from 0.43.0 to 0.44.0
- [Release notes](https://github.com/golang/tools/releases)
- [Commits](https://github.com/golang/tools/compare/v0.43.0...v0.44.0)

---
updated-dependencies:
- dependency-name: golang.org/x/mod
  dependency-version: 0.35.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: extractor-dependencies
- dependency-name: golang.org/x/tools
  dependency-version: 0.44.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: extractor-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-04-14 03:04:45 +00:00
Tom Hvitved
f6fb613962 Merge pull request #21700 from hvitved/js/fastify-per-route-rate-limiting
JS: Recognize Fastify per-route rate limiting
2026-04-13 17:28:34 +02:00
Jeroen Ketema
e0ce5bcf40 Merge pull request #21699 from jketema/join-fix
C++: Fix `isCompiledAsC` join order
2026-04-13 16:03:38 +02:00
Jeroen Ketema
19c4b2ff8f C++: Use getConvSpecString instead of getConvSpecOffset and substring 2026-04-13 15:44:41 +02:00
Anders Schack-Mulligen
d3e580fd0e C#: Introduce Expr.getIntValue. 2026-04-13 14:52:38 +02:00
Taus
2eeb31b472 Python: Add tests for lazy from ... import * as well 2026-04-13 11:49:06 +00:00
Taus
81468daf9c Merge pull request #21603 from github/tausbn/python-port-use-of-exit
Python: Port UseOfExit.ql
2026-04-13 13:20:29 +02:00
Taus
720ea702fe Merge pull request #21602 from github/tausbn/python-port-modification-of-locals
Python: Port ModificationOfLocals.ql
2026-04-13 13:19:40 +02:00
Taus
36bbc8ca14 Merge pull request #21601 from github/tausbn/python-port-unused-exception-object
Python: Port UnusedExceptionObject.ql
2026-04-13 13:19:12 +02:00
Taus
cc9bc746a1 Merge pull request #21597 from github/tausbn/python-port-unreachable-code
Python: Port UnreachableCode.ql
2026-04-13 13:17:59 +02:00
Tom Hvitved
fcfb8c9c6b Add change note 2026-04-13 12:22:30 +02:00
Tom Hvitved
7a48409e38 JS: Recognize Fastify per-route rate limiting 2026-04-13 11:31:34 +02:00
Tom Hvitved
fef582c858 JS: Add test case for Fastify per-route rate limiting 2026-04-13 11:24:41 +02:00
Jeroen Ketema
bee39c9d51 C++: Fix isCompiledAsC join order
Before on Abseil Windows for `cpp/too-few-arguments:`:
```
Pipeline standard for TooFewArguments::isCompiledAsC/1#52fe29e8@994f9bgp was evaluated in 12 iterations totaling 2ms (delta sizes total: 50).
        1198778   ~3%    {1} r1 = JOIN `TooFewArguments::isCompiledAsC/1#52fe29e8#prev_delta` WITH `Element::Element.getFile/0#2b8c8740_10#join_rhs` ON FIRST 1 OUTPUT Rhs.1
             83  ~26%    {1}    | JOIN WITH includes ON FIRST 1 OUTPUT Rhs.1
             50   ~4%    {1}    | AND NOT `TooFewArguments::isCompiledAsC/1#52fe29e8#prev`(FIRST 1)
                         return r1
```

After:
```
Pipeline standard for #File::File.getAnIncludedFile/0#dispred#e8d44cd1Plus#bf@b8d290i6 was evaluated in 11 iterations totaling 0ms (delta sizes total: 43).
        47   ~0%    {2} r1 = SCAN `#File::File.getAnIncludedFile/0#dispred#e8d44cd1Plus#bf#prev_delta` OUTPUT In.1, In.0
        78  ~28%    {2}    | JOIN WITH `File::File.getAnIncludedFile/0#dispred#e8d44cd1` ON FIRST 1 OUTPUT Lhs.1, Rhs.1
        43   ~0%    {2}    | AND NOT `#File::File.getAnIncludedFile/0#dispred#e8d44cd1Plus#bf#prev`(FIRST 2)
                    return r1

[2026-04-13 11:05:25] Evaluated non-recursive predicate TooFewArguments::isCompiledAsC/1#52fe29e8@4a3eb9jk in 0ms (size: 49).
Evaluated relational algebra for predicate TooFewArguments::isCompiledAsC/1#52fe29e8@4a3eb9jk with tuple counts:
         1   ~0%    {3} r1 = CONSTANT(unique int, unique string, unique string)[1,"compiled as c","1"]
         1   ~0%    {1}    | JOIN WITH #fileannotationsMerge_1230#join_rhs ON FIRST 3 OUTPUT Rhs.3

        48   ~0%    {1} r2 = JOIN r1 WITH `#File::File.getAnIncludedFile/0#dispred#e8d44cd1Plus#bf` ON FIRST 1 OUTPUT Rhs.1

        49   ~0%    {1} r3 = r1 UNION r2
                    return r3
```
2026-04-13 11:13:52 +02:00
Tom Hvitved
40eff6525d Rust: Replace special handling of index expressions 2026-04-13 10:30:01 +02:00
Anders Schack-Mulligen
88160ef2e2 C#: Add change note. 2026-04-13 10:05:30 +02:00
Geoffrey White
ae85ada669 Merge pull request #21634 from geoffw0/compwidertype2
C++: Upgrade cpp/comparison-with-wider-type to high precision
2026-04-10 16:08:11 +01:00
Taus
86020d9eed Python: Add change note 2026-04-10 14:43:30 +00:00
Taus
6b7d47ee7d Python: Add QL test for the new syntax 2026-04-10 14:39:13 +00:00
Taus
1ddfed6b6b Python: Add QL support for lazy imports
Adds a new `isLazy` predicate to the relevant classes, and adds the
relevant dbscheme (and up/downgrade) changes. On upgrades we do nothing,
and on downgrades we remove the `is_lazy` bits.
2026-04-10 14:25:08 +00:00
Taus
fe94828fe4 Python: Add overlay annotations to AST template
Otherwise these will disappear every time we regenerate the AST.
2026-04-10 14:23:29 +00:00
Taus
2c79f9d828 Python: Regenerate parser files 2026-04-10 13:50:59 +00:00
Taus
ad4018f399 Python: Add parser support for lazy imports
As defined in PEP-810. We implement this in much the same way as how we
handle `async` annotations currently. The relevant nodes get an
`is_lazy` field that defaults to being false.
2026-04-10 13:50:43 +00:00
Anders Schack-Mulligen
d5c9fd1085 C#/Cfg: A bit more qldoc. 2026-04-10 15:47:25 +02:00
Anders Schack-Mulligen
452913f336 C#: Improve perf of UnsynchronizedStaticAccess.ql. 2026-04-10 15:47:25 +02:00
Anders Schack-Mulligen
aaf9bb2e9e C#: Accept fewer CallContextSpecificCall due to no splitting. 2026-04-10 15:47:24 +02:00
Anders Schack-Mulligen
2d5a1840f4 C#: Accept new CFG in tests. 2026-04-10 15:47:24 +02:00
Anders Schack-Mulligen
bbd403dbc3 C#: Rework DataFlowCallable-to-cfg relation in terms of basic blocks for performance. 2026-04-10 15:47:23 +02:00
Anders Schack-Mulligen
bfbd0f77e8 C#: Fix some bad join orders. 2026-04-10 15:47:23 +02:00
Anders Schack-Mulligen
1d9c0ae388 C#: Fix perf. 2026-04-10 15:47:22 +02:00
Anders Schack-Mulligen
371bc3012e C#: CFG and data flow nodes now exist for LHSs. 2026-04-10 15:47:22 +02:00
Anders Schack-Mulligen
a7d4b00d06 C#: Accept changed location for phi nodes. 2026-04-10 15:47:21 +02:00
Anders Schack-Mulligen
a69581966b C#: Accept CFG changes for "first" relation. 2026-04-10 15:47:21 +02:00
Anders Schack-Mulligen
a997d9f80c C#: Accept fixed consistency check. 2026-04-10 15:47:20 +02:00
Anders Schack-Mulligen
773881f333 C#: Accept data flow inconsistency check for read+write calls. 2026-04-10 15:47:20 +02:00
Anders Schack-Mulligen
88256eeee8 C#: GuardedExpr no longer contains expressions guarded solely by disjunctions. 2026-04-10 15:47:19 +02:00
Anders Schack-Mulligen
e90243c348 C#: Accept irrelevant changes.
The additions are unintentional, but the fault lies with the shared
SignAnalysis code. The removals are due to compile-time constant
initializers no longer having CFG nodes.
2026-04-10 15:47:19 +02:00
Anders Schack-Mulligen
49cc931f92 C#: Compile-time constants no longer have CFG nodes. 2026-04-10 15:47:18 +02:00
Anders Schack-Mulligen
5d589093cf C#: Accept CFG changes. 2026-04-10 15:47:18 +02:00
Anders Schack-Mulligen
a5c99f9693 C#: Accept harmless CFG changes. 2026-04-10 15:47:17 +02:00
Anders Schack-Mulligen
6010640cea C#: Accept bugfix. 2026-04-10 15:47:17 +02:00
Anders Schack-Mulligen
1a6670a6bb C#: Phi nodes are not expected to have associated Elements. 2026-04-10 15:47:16 +02:00
Anders Schack-Mulligen
43fe411585 C#: Accept SSA location changes. 2026-04-10 15:47:16 +02:00
Anders Schack-Mulligen
093eb57ad0 C#: Fix CFG position of property setter calls. 2026-04-10 15:47:15 +02:00
Anders Schack-Mulligen
ac88b73b65 C#: Bugfix in enclosing callable. 2026-04-10 15:47:15 +02:00
Anders Schack-Mulligen
700d56f3ab C#: Fix UncheckedCastInEquals. 2026-04-10 15:47:14 +02:00
Anders Schack-Mulligen
b1790335c0 C#: Fix test. 2026-04-10 15:47:14 +02:00
Anders Schack-Mulligen
ff978d1a8c C#: Replace CFG. 2026-04-10 15:47:13 +02:00
Anders Schack-Mulligen
9cf9a36d0d C#: Rename ControlFlow::BasicBlock to BasicBlock. 2026-04-10 15:47:12 +02:00
Anders Schack-Mulligen
13a4141cc6 C#: Rename remaining references to ControlFlow::Nodes. 2026-04-10 15:47:12 +02:00
Anders Schack-Mulligen
b878ae3f21 C#: Update some references to ControlFlow::Nodes. 2026-04-10 15:47:11 +02:00
Anders Schack-Mulligen
03f6bdbdd2 C#: Update some references in preparation for CFG swap. 2026-04-10 15:47:11 +02:00
Anders Schack-Mulligen
b85b02abb4 Cfg: Add dominance predicates to shared ControlFlowNode. 2026-04-10 15:47:10 +02:00
Anders Schack-Mulligen
61976e3ef0 C#: Rename ControlFlow::Node to ControlFlowNode. 2026-04-10 15:47:10 +02:00
Anders Schack-Mulligen
88aaff863b Cfg: Extend consistency checks. 2026-04-10 15:47:09 +02:00
Anders Schack-Mulligen
6ffed8523c Cfg/Java: Move InstanceOfExpr CFG into shared lib. 2026-04-10 15:47:09 +02:00
Anders Schack-Mulligen
035b83c0e4 C#: Introduce ControlFlowElementOrCallable. 2026-04-10 15:47:08 +02:00
Anders Schack-Mulligen
0b6c416fd4 Cfg: Support short-circuiting compound assignments. 2026-04-10 15:47:08 +02:00
Anders Schack-Mulligen
a53cffc121 Cfg: Support GotoStmt. 2026-04-10 15:47:07 +02:00
Anders Schack-Mulligen
93a594e9c0 Cfg: Support Throw expressions. 2026-04-10 15:47:07 +02:00
Taus
6078df524b Merge pull request #21683 from github/tausbn/python-add-extractor-pack-build-script
Python: Add `create-extractor-pack.sh` for Python
2026-04-10 15:16:54 +02:00
Jeroen Ketema
888d392040 Merge pull request #21636 from jketema/actions-perm
Actions: Correctly check reusable workflow permissions in `actions/missing-workflow-permissions`
2026-04-10 15:02:36 +02:00
Geoffrey White
b9226a359a Merge pull request #21633 from geoffw0/intmultlong2
C++: Upgrade cpp/integer-multiplication-cast-to-long to high precision
2026-04-10 14:02:34 +01:00
Geoffrey White
814c0ae7a8 Merge pull request #21632 from geoffw0/wrongtype2
C++: Upgrade cpp/wrong-type-format-argument to high precision
2026-04-10 14:01:07 +01:00
Geoffrey White
9ea33bc5bb Merge pull request #21553 from geoffw0/implicitfn
C++: Disable cpp/implicit-function-declaration on build mode none databases
2026-04-10 14:00:06 +01:00
Geoffrey White
bcf612e6fe Merge branch 'main' into compwidertype2 2026-04-10 13:58:35 +01:00
Anders Schack-Mulligen
dfa8d72dd3 Merge pull request #21685 from aschackmull/csharp/unbind-new
C#: Replace old-style unbind with pragmas.
2026-04-10 13:55:01 +02:00
Tom Hvitved
27f7f747a4 Rust: Check whole blanket constraints, not just the root trait type 2026-04-10 13:20:36 +02:00
Tom Hvitved
be329c8ab4 Rust: Replace recursion through forall with ranked recursion 2026-04-10 13:18:57 +02:00
Geoffrey White
bcdbf141bc Merge pull request #21671 from geoffw0/neutralperf
Rust: Fix performance issue with additionalExternalFile
2026-04-10 12:08:27 +01:00
Geoffrey White
0714ca816a Merge branch 'main' into suspicioussizeof2 2026-04-10 10:10:45 +01:00
Tom Hvitved
42fe2d5002 Rust: Add another type inference test 2026-04-10 10:18:54 +02:00
Paolo Tranquilli
7de8ce961c Merge pull request #21677 from github/dependabot/bazel/gazelle-0.50.0
Bump gazelle from 0.47.0 to 0.50.0
2026-04-10 10:07:25 +02:00
Michael Nebel
66278fcd10 Merge pull request #21690 from samchang-msft/update-net10-support
Support added in Jan 2026
2026-04-10 08:40:29 +02:00
Sam Chang
7883fab44f Qualify the limited support for .NET 10 and C# 14 2026-04-09 12:06:54 -07:00
Sam Chang
38440d96b8 Support added in Jan 2026 2026-04-09 10:48:08 -07:00
Jeroen Ketema
43f48001e3 Swift: Clear override 2026-04-09 16:32:43 +02:00
Jeroen Ketema
4ada727bab Swift: Add staged archives to LFS 2026-04-09 16:32:36 +02:00
Anders Schack-Mulligen
cf4ab1d106 C#: Replace old-style unbind with pragmas. 2026-04-09 15:57:19 +02:00
Tom Hvitved
23f081006e Rust: Track closure types in data flow 2026-04-09 15:25:52 +02:00
Tom Hvitved
3fa5c952b3 Rust: Add more closure flow tests 2026-04-09 15:25:50 +02:00
Jeroen Ketema
85c42ae932 Swift: Update supported versions 2026-04-09 15:19:29 +02:00
Jeroen Ketema
94fb011b90 Swift: Add change note 2026-04-09 15:17:13 +02:00
Taus
d622dabf3e Python: Add create-extractor-pack.sh for Python
This allows us to build and test the extractor (for actual QL extraction
-- not just the extractor unit tests) entirely from within the
`github/codeql` repo, just as we do with Ruby. All that's needed is a
`--search-path` argument that points to the repo root.
2026-04-09 13:06:45 +00:00
Jeroen Ketema
21937c2415 Swift: Add dbscheme upgrade and downgrade scripts 2026-04-09 15:05:30 +02:00
Jeroen Ketema
7879d0a006 Swift: Fix OpaqueTypeArchetypeType name mangling 2026-04-09 15:05:28 +02:00
Jeroen Ketema
34b626e8bb Swift: Update expected integration test results 2026-04-09 15:05:27 +02:00
Jeroen Ketema
d09e2f66cd Swift: Assign indexes to fileprivate ValueDecls
At least in the case of function declarations there can be multiple
identical ones within the same module, causing data set check errors
if not differentiated.
2026-04-09 15:05:16 +02:00
Tom Hvitved
33cc887be0 Merge pull request #21592 from hvitved/dataflow/source-call-context-type-flow
Data flow: Add hook for preventing lambda dispatch in source call contexts
2026-04-09 13:44:42 +02:00
Geoffrey White
e72c116664 Rust: Proposed improved solution. 2026-04-09 11:18:25 +01:00
Tom Hvitved
d704b753c8 Fix CP in typeFlowParamType
Forgot to link `p` with `c` using `nodeEnclosingCallable(p, c)`.
2026-04-09 09:19:55 +02:00
dependabot[bot]
7833a0a2e8 Bump gazelle from 0.47.0 to 0.50.0
Bumps [gazelle](https://github.com/bazel-contrib/bazel-gazelle) from 0.47.0 to 0.50.0.
- [Release notes](https://github.com/bazel-contrib/bazel-gazelle/releases)
- [Commits](https://github.com/bazel-contrib/bazel-gazelle/compare/v0.47.0...v0.50.0)

---
updated-dependencies:
- dependency-name: gazelle
  dependency-version: 0.50.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-04-09 03:08:02 +00:00
Geoffrey White
95681bfad4 Rust: Fix performance issue with File.fromSource. 2026-04-08 15:04:03 +01:00
Jeroen Ketema
7bf78de167 Swift: Fix AnyFunctionType name mangling 2026-04-08 15:53:24 +02:00
Kristen Newbury
fb0ee5b987 Merge pull request #21640 from knewbury01/knewbury01/adjust-actions-queries-alerts
Adjust alert messages CWE-829/ArtifactPoisoning[Critical|Medium]
2026-04-08 09:44:00 -04:00
Jeroen Ketema
f7de0abe60 Swift: Fix BuiltinFixedArrayType mangling 2026-04-08 15:41:57 +02:00
Kristen Newbury
7b7411f7df Change alert location CWE-829/ArtifactPoisoning queries 2026-04-08 08:57:45 -04:00
Jeroen Ketema
5eb8db0d48 Swift: Update expected QL test results after 6.3 update 2026-04-08 13:21:33 +02:00
Jeroen Ketema
6b2494c3e5 Swift: Update generated files 2026-04-08 13:21:03 +02:00
Jeroen Ketema
d473c7143d Swift: Update schema 2026-04-08 13:20:06 +02:00
Jeroen Ketema
fd83515843 Swift: Make extractor compile 2026-04-08 13:19:40 +02:00
Jeroen Ketema
2fbfcb970e Swift: Use Swift 6.3 artifacts 2026-04-08 13:19:00 +02:00
Taus
e3688444d7 Python: Also exclude class scope
Changing the `locals()` dictionary actually _does_ change the attributes
of the class being defined, so we shouldn't alert in this case.
2026-04-07 23:46:03 +02:00
Taus
8d79248ea7 Python: Port ModificationOfLocals.ql 2026-04-07 23:46:03 +02:00
Taus
16683aee0e Merge pull request #21590 from github/tausbn/python-improve-bind-all-interfaces-query
Python: Improve "bind all interfaces" query
2026-04-07 17:59:48 +02:00
Jeroen Ketema
e7d3eedc80 Merge pull request #21661 from jketema/autoconf
C++: Add heuristic for GNU autoconf config files
2026-04-07 15:38:06 +02:00
Taus
4cb238f1af Merge pull request #21598 from github/tausbn/python-port-should-use-with
Python: Port ShouldUseWithStatement.ql
2026-04-07 14:16:41 +02:00
Geoffrey White
b21dba6131 C++: Update code scanning suite .expected. 2026-04-07 13:06:34 +01:00
Geoffrey White
201af3fffc C++: Update code scanning suite .expected. 2026-04-07 12:59:31 +01:00
Geoffrey White
f2292643a3 C++: Update code scanning suite .expected. 2026-04-07 12:53:53 +01:00
Geoffrey White
3769a8a482 C++: Update code scanning suite .expected. 2026-04-07 12:51:56 +01:00
Mathias Vorreiter Pedersen
5e145aa27d Merge pull request #21631 from MathiasVP/expose-fwd-stage-1
Dataflow: Expose stage 1's `fwdFlow`
2026-04-07 11:29:56 +01:00
Mathias Vorreiter Pedersen
e06294bcb4 Shared: Respond to review comments. 2026-04-07 11:11:04 +01:00
Idriss Riouak
39f92e992a Merge pull request #21494 from github/idrissrio/java/jdk26
Java: Accept new test results after JDK 26 extractor upgrade
2026-04-07 12:03:36 +02:00
Tom Hvitved
0d4524f8f3 Address review comments 2026-04-07 11:40:10 +02:00
Tom Hvitved
1e1a8732a3 Data flow: Add hook for preventing lambda dispatch in source call contexts 2026-04-07 11:40:08 +02:00
Tom Hvitved
eb64fcd208 C#: Add test that shows unintended flow summary generation 2026-04-07 11:40:07 +02:00
Jeroen Ketema
04cfd37f53 C++: Fix comments in tests 2026-04-07 10:52:12 +02:00
Jeroen Ketema
b19c648965 C++: Add heuristic for GNU autoconf config files 2026-04-07 10:43:15 +02:00
Michael Nebel
e259ebe258 Merge pull request #21627 from michaelnebel/csharp/cleanup
C#: Deprecate get[L|R]Value predicates.
2026-04-07 10:23:59 +02:00
idrissrio
6f199b90ba Java: Accept new test results for JDK 26
Accept new ByteOrder.getEntries, List.ofLazy, and Map.ofLazy entries
in kotlin2 test expected files.
2026-04-07 09:28:25 +02:00
idrissrio
3ccbd8032c Java: Accept new test results for JDK 26
JDK 26 added ofLazy methods to List, Map, and Set collections.
Update expected test output to include these new methods.
2026-04-07 09:28:23 +02:00
idrissrio
5a6eb79470 Java: Pin CWE-676 test to --release 25
Thread.stop() was removed in JDK 26. Pin the test to --release 25.
2026-04-07 09:28:22 +02:00
idrissrio
74b0e8c19a Java: Accept new test results after JDK 26 extractor upgrade 2026-04-07 09:28:20 +02:00
Tom Hvitved
7d184d0c7f Merge pull request #21206 from hvitved/rust/type-inference-closure-param-context-typed
Rust: Infer argument types based on trait bounds on parameters
2026-04-07 09:17:30 +02:00
github-actions[bot]
242090e0ac Post-release preparation for codeql-cli-2.25.2 2026-04-06 13:49:20 +00:00
MarkLee131
46ef0204ef Remove secretQuestion from FP exclusion list
secretQuestion is ambiguous: it could be the question text (not
sensitive) or a security question answer. Worse, the regex
secrets?(question) also matches secretQuestionAnswer, which is
clearly sensitive. Drop it to avoid false negatives.
2026-04-04 21:58:32 +08:00
MarkLee131
20cfe29199 Java: reduce false positives in sensitive-log by expanding FP exclusion regex
The getCommonSensitiveInfoFPRegex() only excluded "null", "tokenizer", and
"tokenImage", causing widespread false positives for common non-sensitive
variable names containing "token" or "secret".

This adds exclusions for three categories:
- Pagination/iteration tokens: nextToken (AWS SDK), pageToken (GCP),
  continuationToken (Azure), etc.
- Token metadata: tokenType (OAuth), tokenEndpoint (OIDC), tokenCount,
  tokenIndex, tokenLength, tokenUrl, etc.
- Secret metadata: secretName (K8s/AWS), secretId (Azure),
  secretVersion, secretArn, secretPath, etc.

All truly sensitive variable names (accessToken, clientSecret, secretKey,
refreshToken, etc.) remain correctly flagged.
2026-04-04 21:33:35 +08:00
MarkLee131
9ff4ed286f Java: recognize Path.toRealPath() as path normalization sanitizer
PathNormalizeSanitizer recognized Path.normalize() and
File.getCanonicalPath()/getCanonicalFile(), but not Path.toRealPath().

toRealPath() is strictly stronger than normalize() (resolves symlinks
and verifies file existence in addition to normalizing ".." components),
and is functionally equivalent to File.getCanonicalPath() for the NIO.2
API. CERT FIO16-J and OWASP both recommend it for path traversal defense.

This adds toRealPath to PathNormalizeSanitizer alongside normalize,
reducing false positives for code using idiomatic NIO.2 path handling.
2026-04-04 20:59:45 +08:00
Kristen Newbury
41714656ec Adjust alert messages actions CWE-829 2026-04-02 11:58:58 -04:00
Kristen Newbury
e69e30aa84 Adjust alert messages CWE-829/ArtifactPoisoning[Critical|Medium] 2026-04-02 11:32:37 -04:00
Jeroen Ketema
87f9b9581e Actions: Add change note 2026-04-02 15:48:45 +02:00
Jeroen Ketema
47409d1c59 Actions: Update expected test results 2026-04-02 15:43:49 +02:00
Jeroen Ketema
74e6d3474d Actions: Correctly check permissions in actions/missing-workflow-permissions 2026-04-02 15:42:45 +02:00
Jeroen Ketema
5866bcc881 Actions: Add FP test for actions/missing-workflow-permissions 2026-04-02 15:41:41 +02:00
Geoffrey White
cc89b6ea91 C++: Change note. 2026-04-02 11:52:37 +01:00
Geoffrey White
70b72f70e1 C++: Upgrade query precision. 2026-04-02 11:52:36 +01:00
Geoffrey White
56af9a84ab Update cpp/ql/src/Likely Bugs/Underspecified Functions/ImplicitFunctionDeclaration.qhelp 2026-04-02 11:40:51 +01:00
Geoffrey White
9eabfc5fdc Update cpp/ql/src/Likely Bugs/Underspecified Functions/ImplicitFunctionDeclaration.ql
Co-authored-by: Jeroen Ketema <93738568+jketema@users.noreply.github.com>
2026-04-02 11:39:45 +01:00
Geoffrey White
e83658ed06 C++: Upgrade query precision. 2026-04-02 11:38:09 +01:00
Geoffrey White
2d02056e5c C++: Second change note. 2026-04-02 11:34:54 +01:00
Geoffrey White
9dbbdef4cb C++: Change note. 2026-04-02 11:30:52 +01:00
Geoffrey White
520e95d92c C++: Upgrade query precision. 2026-04-02 11:30:34 +01:00
Geoffrey White
909b55a40a C++: Change note. 2026-04-02 11:28:34 +01:00
Geoffrey White
b41a4ff5e4 C++: Upgrade query precision. 2026-04-02 11:28:19 +01:00
Geoffrey White
fca567f6ea C++: Change note. 2026-04-02 11:26:50 +01:00
Geoffrey White
84c01bc255 C++: Upgrade query precision. 2026-04-02 11:26:49 +01:00
Mathias Vorreiter Pedersen
4d8b782695 Shared: Also expose dataflow stage 1's forward flow predicate. 2026-04-02 10:56:09 +01:00
Michael Nebel
6d5aff4822 C#: Add change-note. 2026-04-01 13:17:52 +02:00
Michael Nebel
9c095bc580 C#: Deprecate get[L|R]Value predicates. 2026-04-01 12:50:37 +02:00
Taus
a0b3c2f13a Python: Update change note
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-03-27 23:46:50 +01:00
Taus
187f7c7bcf Python: Move isNetworkBind check into isSink 2026-03-27 22:45:26 +00:00
Owen Mansel-Chan
37aac05964 Replace branch with acceptingValue 2026-03-27 22:39:10 +00:00
Taus
c5ef1f6342 Python: Port UseOfExit.ql 2026-03-27 22:28:38 +00:00
Owen Mansel-Chan
a7fdc4b543 Replace acceptingvalue with acceptingValue 2026-03-27 22:15:45 +00:00
Geoffrey White
a9cce1c0fa C++: Undo increasing query precision. 2026-03-27 17:32:03 +00:00
Geoffrey White
4f3108c444 C++: Update change note. 2026-03-27 17:04:05 +00:00
Taus
4f74d421b9 Python: Exclude AF_UNIX sockets from BindToAllInterfaces
Looking at the results of the the previous DCA run, there was a bunch of
false positives where `bind` was being used with a `AF_UNIX` socket (a
filesystem path encoded as a string), not a `(host, port)` tuple. These
results should be excluded from the query, as they are not vulnerable.

Ideally, we would just add `.TupleElement[0]` to the MaD sink, except we
don't actually support this in Python MaD...

So, instead I opted for a more low-tech solution: check that the
argument in question flows from a tuple in the local scope.

This eliminates a bunch of false positives on `python/cpython` leaving
behind four true positive results.
2026-03-27 16:55:10 +00:00
Geoffrey White
50681a3c42 C++: Add note to the .qhelp. 2026-03-27 16:47:31 +00:00
Geoffrey White
bb9873dc8f C++: Increase the query precision to high. 2026-03-27 16:40:45 +00:00
Taus
47d24632e6 Python: Port ShouldUseWithStatement.ql
Only trivial test changes.
2026-03-27 12:34:20 +00:00
Taus
0ea80ac184 Python: Port UnusedExceptionObject.ql
Depending on whether other queries depend on this, we may end up moving
the exception utility functions to a more central location.
2026-03-27 12:34:14 +00:00
Taus
60f9ce4ce7 Python: Port UnreachableCode.ql 2026-03-27 12:33:04 +00:00
Owen Mansel-Chan
b3285c6ae2 Make description of acceptingvalue column clearer 2026-03-27 11:35:22 +00:00
Tom Hvitved
6dc98cfd01 Rust: Infer argument types based on trait bounds on parameters 2026-03-27 11:39:03 +01:00
Owen Mansel-Chan
5451424e75 Rust: Fix columns for neutrals 2026-03-27 09:47:36 +00:00
Owen Mansel-Chan
886a16bfad C++: Add provenance column 2026-03-27 09:47:34 +00:00
Owen Mansel-Chan
e680d49c93 Shared: document extensible relations rather than CSV 2026-03-27 09:47:32 +00:00
Owen Mansel-Chan
df842665b7 Rust: Add neutrals to MaD format explanation 2026-03-27 09:47:30 +00:00
Owen Mansel-Chan
805d2ec46c Go: Add provenance to MaD format explanation 2026-03-27 09:47:28 +00:00
Owen Mansel-Chan
61b13d5702 C++: Add provenance to MaD format explanation 2026-03-27 09:47:26 +00:00
Owen Mansel-Chan
10fddc7b96 Add barriers and barrier guards to MaD format explanations 2026-03-27 09:47:24 +00:00
Taus
c9832c330a Python: Convert BindToAllInterfaces to path-problem
Now that we're using global data-flow, we might as well make use of the
fact that we know where the source is.
2026-03-26 21:10:43 +00:00
Tom Hvitved
b8a8a160c5 Rust: More type inference tests 2026-03-26 18:06:32 +01:00
Taus
c0ce6699a5 Python: Add change note 2026-03-26 15:35:33 +00:00
Taus
c439fc5d45 Python: Replace type tracking with global data-flow
This takes care of most of the false negatives from the preceding
commit.

Additionally, we add models for some known wrappers of `socket.socket`
from the `gevent` and `eventlet` packages.
2026-03-26 15:35:33 +00:00
Taus
1ecd9e83b8 Python: Add test cases for BindToAllInterfaces FNs
Adds test cases from github/codeql#21582 demonstrating false negatives:
- Address stored in class attribute (`self.bind_addr`)
- `os.environ.get` with insecure default value
- `gevent.socket` (alternative socket module)
2026-03-26 14:57:24 +00:00
Taus
824d004a27 Python: Convert BindToAllInterfaces test to inline expectations 2026-03-26 14:56:57 +00:00
Paolo Tranquilli
e0bc18c228 Add changenote for false positive sink model removals 2026-03-26 09:19:34 +01:00
Paolo Tranquilli
e807545591 Remove false positive docker/build-push-action context sink model
The `context` input is passed as a single array element through
`docker/actions-toolkit` and `@actions/exec` all the way to
`child_process.spawn()`, which does not perform shell splitting.
No code injection is possible.

Fixes https://github.com/github/codeql/issues/21428
2026-03-26 09:08:34 +01:00
Paolo Tranquilli
55d16e8781 Remove false-positive command-injection sink model for step-security/harden-runner
The `allowed-endpoints` input only flows to `execFileSync("echo", [content])`
(no shell) and `fs.writeFileSync` (JSON config), neither of which is a
command injection vector.

Fixes https://github.com/github/codeql/issues/21568
2026-03-25 10:58:16 +01:00
Geoffrey White
39056e4477 C++: Change note. 2026-03-23 12:28:17 +00:00
Geoffrey White
5a77128a8b C++: Disable cpp/implicit-function-declaration on BMN databases. 2026-03-23 11:27:15 +00:00
copilot-swe-agent[bot]
b6004045bd Clean up Go version workflow - remove unnecessary escaping and checks
Co-authored-by: mbg <278086+mbg@users.noreply.github.com>
2026-02-13 11:23:44 +00:00
copilot-swe-agent[bot]
cc7e03b0f5 Add error handling and validation to Go version workflow
Co-authored-by: mbg <278086+mbg@users.noreply.github.com>
2026-02-13 11:22:36 +00:00
copilot-swe-agent[bot]
1cbd423251 Improve portability and fix PR detection in Go version workflow
Co-authored-by: mbg <278086+mbg@users.noreply.github.com>
2026-02-13 11:21:13 +00:00
copilot-swe-agent[bot]
437244fe90 Fix portability issues in Go version update workflow
Co-authored-by: mbg <278086+mbg@users.noreply.github.com>
2026-02-13 11:19:56 +00:00
copilot-swe-agent[bot]
f7cf24d1f9 Add Go version update workflow
Co-authored-by: mbg <278086+mbg@users.noreply.github.com>
2026-02-13 11:17:57 +00:00
copilot-swe-agent[bot]
c3bafacf81 Initial plan 2026-02-13 11:15:15 +00:00
636 changed files with 144424 additions and 102169 deletions

206
.github/workflows/go-version-update.yml vendored Normal file
View File

@@ -0,0 +1,206 @@
name: Update Go version
on:
workflow_dispatch:
schedule:
- cron: "0 3 * * 1" # Run weekly on Mondays at 3 AM UTC (1 = Monday)
permissions:
contents: write
pull-requests: write
jobs:
update-go-version:
name: Check and update Go version
if: github.repository == 'github/codeql'
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v5
with:
fetch-depth: 0
- name: Set up Git
run: |
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
- name: Fetch latest Go version
id: fetch-version
run: |
LATEST_GO_VERSION=$(curl -s https://go.dev/dl/?mode=json | jq -r '.[0].version')
if [ -z "$LATEST_GO_VERSION" ] || [ "$LATEST_GO_VERSION" = "null" ]; then
echo "Error: Failed to fetch latest Go version from go.dev"
exit 1
fi
echo "Latest Go version from go.dev: $LATEST_GO_VERSION"
echo "version=$LATEST_GO_VERSION" >> $GITHUB_OUTPUT
# Extract version numbers (e.g., go1.26.0 -> 1.26.0)
LATEST_VERSION_NUM=$(echo $LATEST_GO_VERSION | sed 's/^go//')
echo "version_num=$LATEST_VERSION_NUM" >> $GITHUB_OUTPUT
# Extract major.minor version (e.g., 1.26.0 -> 1.26)
LATEST_MAJOR_MINOR=$(echo $LATEST_VERSION_NUM | sed -E 's/^([0-9]+\.[0-9]+).*/\1/')
echo "major_minor=$LATEST_MAJOR_MINOR" >> $GITHUB_OUTPUT
- name: Check current Go version
id: current-version
run: |
CURRENT_VERSION=$(sed -n 's/.*go_sdk\.download(version = \"\([^\"]*\)\".*/\1/p' MODULE.bazel)
if [ -z "$CURRENT_VERSION" ]; then
echo "Error: Could not extract Go version from MODULE.bazel"
exit 1
fi
echo "Current Go version in MODULE.bazel: $CURRENT_VERSION"
echo "version=$CURRENT_VERSION" >> $GITHUB_OUTPUT
# Extract major.minor version
CURRENT_MAJOR_MINOR=$(echo $CURRENT_VERSION | sed -E 's/^([0-9]+\.[0-9]+).*/\1/')
echo "major_minor=$CURRENT_MAJOR_MINOR" >> $GITHUB_OUTPUT
- name: Compare versions
id: compare
run: |
LATEST="${{ steps.fetch-version.outputs.version_num }}"
CURRENT="${{ steps.current-version.outputs.version }}"
echo "Latest: $LATEST"
echo "Current: $CURRENT"
if [ "$LATEST" = "$CURRENT" ]; then
echo "Go version is up to date"
echo "needs_update=false" >> $GITHUB_OUTPUT
else
echo "Go version needs update from $CURRENT to $LATEST"
echo "needs_update=true" >> $GITHUB_OUTPUT
fi
- name: Update Go version in files
if: steps.compare.outputs.needs_update == 'true'
run: |
LATEST_VERSION_NUM="${{ steps.fetch-version.outputs.version_num }}"
LATEST_MAJOR_MINOR="${{ steps.fetch-version.outputs.major_minor }}"
CURRENT_VERSION="${{ steps.current-version.outputs.version }}"
CURRENT_MAJOR_MINOR="${{ steps.current-version.outputs.major_minor }}"
echo "Updating from $CURRENT_VERSION to $LATEST_VERSION_NUM"
# Escape dots in current version strings for use in sed patterns
CURRENT_VERSION_ESCAPED=$(echo "$CURRENT_VERSION" | sed 's/\./\\./g')
CURRENT_MAJOR_MINOR_ESCAPED=$(echo "$CURRENT_MAJOR_MINOR" | sed 's/\./\\./g')
# Update MODULE.bazel
if ! sed -i "s/go_sdk\.download(version = \"$CURRENT_VERSION_ESCAPED\")/go_sdk.download(version = \"$LATEST_VERSION_NUM\")/" MODULE.bazel; then
echo "Warning: Failed to update MODULE.bazel"
fi
# Update go/extractor/go.mod
if ! sed -i "s/^go $CURRENT_MAJOR_MINOR_ESCAPED\$/go $LATEST_MAJOR_MINOR/" go/extractor/go.mod; then
echo "Warning: Failed to update go directive in go.mod"
fi
if ! sed -i "s/^toolchain go$CURRENT_VERSION_ESCAPED\$/toolchain go$LATEST_VERSION_NUM/" go/extractor/go.mod; then
echo "Warning: Failed to update toolchain in go.mod"
fi
# Update go/extractor/autobuilder/build-environment.go
if ! sed -i "s/var maxGoVersion = util\.NewSemVer(\"$CURRENT_MAJOR_MINOR_ESCAPED\")/var maxGoVersion = util.NewSemVer(\"$LATEST_MAJOR_MINOR\")/" go/extractor/autobuilder/build-environment.go; then
echo "Warning: Failed to update build-environment.go"
fi
# Update go/actions/test/action.yml
if ! sed -i "s/default: \"~$CURRENT_VERSION_ESCAPED\"/default: \"~$LATEST_VERSION_NUM\"/" go/actions/test/action.yml; then
echo "Warning: Failed to update action.yml"
fi
# Show what changed
git diff
- name: Check for changes
id: check-changes
if: steps.compare.outputs.needs_update == 'true'
run: |
if git diff --quiet; then
echo "No changes detected"
echo "has_changes=false" >> $GITHUB_OUTPUT
else
echo "Changes detected"
echo "has_changes=true" >> $GITHUB_OUTPUT
fi
- name: Check for existing PR
if: steps.check-changes.outputs.has_changes == 'true'
id: check-pr
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
BRANCH_NAME="workflow/go-version-update"
PR_NUMBER=$(gh pr list --head "$BRANCH_NAME" --state open --json number --jq '.[0].number')
if [ -n "$PR_NUMBER" ]; then
echo "Existing PR found: #$PR_NUMBER"
echo "pr_exists=true" >> $GITHUB_OUTPUT
echo "pr_number=$PR_NUMBER" >> $GITHUB_OUTPUT
else
echo "No existing PR found"
echo "pr_exists=false" >> $GITHUB_OUTPUT
fi
- name: Commit and push changes
if: steps.check-changes.outputs.has_changes == 'true'
run: |
BRANCH_NAME="workflow/go-version-update"
LATEST_VERSION_NUM="${{ steps.fetch-version.outputs.version_num }}"
LATEST_MAJOR_MINOR="${{ steps.fetch-version.outputs.major_minor }}"
# Create or switch to branch
git checkout -B "$BRANCH_NAME"
# Stage and commit changes
git add MODULE.bazel go/extractor/go.mod go/extractor/autobuilder/build-environment.go go/actions/test/action.yml
git commit -m "Go: Update to $LATEST_MAJOR_MINOR"
# Push changes
git push -f origin "$BRANCH_NAME"
- name: Create or update PR
if: steps.check-changes.outputs.has_changes == 'true'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
BRANCH_NAME="workflow/go-version-update"
LATEST_MAJOR_MINOR="${{ steps.fetch-version.outputs.major_minor }}"
CURRENT_MAJOR_MINOR="${{ steps.current-version.outputs.major_minor }}"
PR_TITLE="Go: Update to $LATEST_MAJOR_MINOR"
PR_BODY=$(cat <<EOF
This PR updates Go from $CURRENT_MAJOR_MINOR to $LATEST_MAJOR_MINOR.
Updated files:
- \`MODULE.bazel\` - go_sdk.download version
- \`go/extractor/go.mod\` - go directive and toolchain
- \`go/extractor/autobuilder/build-environment.go\` - maxGoVersion
- \`go/actions/test/action.yml\` - default go-test-version
This PR was automatically created by the [Go version update workflow](https://github.com/${{ github.repository }}/blob/main/.github/workflows/go-version-update.yml).
EOF
)
if [ "${{ steps.check-pr.outputs.pr_exists }}" = "true" ]; then
echo "Updating existing PR #${{ steps.check-pr.outputs.pr_number }}"
gh pr edit "${{ steps.check-pr.outputs.pr_number }}" --title "$PR_TITLE" --body "$PR_BODY"
else
echo "Creating new PR"
gh pr create \
--title "$PR_TITLE" \
--body "$PR_BODY" \
--base main \
--head "$BRANCH_NAME" \
--label "Go"
fi

View File

@@ -27,7 +27,7 @@ bazel_dep(name = "abseil-cpp", version = "20260107.1", repo_name = "absl")
bazel_dep(name = "nlohmann_json", version = "3.11.3", repo_name = "json")
bazel_dep(name = "fmt", version = "12.1.0-codeql.1")
bazel_dep(name = "rules_kotlin", version = "2.2.2-codeql.1")
bazel_dep(name = "gazelle", version = "0.47.0")
bazel_dep(name = "gazelle", version = "0.50.0")
bazel_dep(name = "rules_dotnet", version = "0.21.5-codeql.1")
bazel_dep(name = "googletest", version = "1.17.0.bcr.2")
bazel_dep(name = "rules_rust", version = "0.69.0")

View File

@@ -1,3 +1,9 @@
## 0.4.34
### Minor Analysis Improvements
* Removed false positive injection sink models for the `context` input of `docker/build-push-action` and the `allowed-endpoints` input of `step-security/harden-runner`.
## 0.4.33
No user-facing changes.

View File

@@ -0,0 +1,5 @@
## 0.4.34
### Minor Analysis Improvements
* Removed false positive injection sink models for the `context` input of `docker/build-push-action` and the `allowed-endpoints` input of `step-security/harden-runner`.

View File

@@ -1,2 +1,2 @@
---
lastReleaseVersion: 0.4.33
lastReleaseVersion: 0.4.34

View File

@@ -1,6 +0,0 @@
extensions:
- addsTo:
pack: codeql/actions-all
extensible: actionsSinkModel
data:
- ["docker/build-push-action", "*", "input.context", "code-injection", "manual"]

View File

@@ -1,6 +0,0 @@
extensions:
- addsTo:
pack: codeql/actions-all
extensible: actionsSinkModel
data:
- ["step-security/harden-runner", "*", "input.allowed-endpoints", "command-injection", "manual"]

View File

@@ -1,5 +1,5 @@
name: codeql/actions-all
version: 0.4.33
version: 0.4.35-dev
library: true
warnOnImplicitThis: true
dependencies:

View File

@@ -1,3 +1,13 @@
## 0.6.26
### Major Analysis Improvements
* Fixed alert messages in `actions/artifact-poisoning/critical` and `actions/artifact-poisoning/medium` as they previously included a redundant placeholder in the alert message that would on occasion contain a long block of yml that makes the alert difficult to understand. Also improved the wording to make it clearer that it is not the artifact that is being poisoned, but instead a potentially untrusted artifact that is consumed. Finally, changed the alert location to be the source, to align more with other queries reporting an artifact (e.g. zipslip) which is more useful.
### Minor Analysis Improvements
* The query `actions/missing-workflow-permissions` no longer produces false positive results on reusable workflows where all callers set permissions.
## 0.6.25
No user-facing changes.
@@ -163,7 +173,7 @@ No user-facing changes.
* `actions/if-expression-always-true/critical`
* `actions/if-expression-always-true/high`
* `actions/unnecessary-use-of-advanced-config`
* The following query has been moved from the `code-scanning` suite to the `security-extended`
suite. Any existing alerts for this query will be closed automatically unless the analysis is
configured to use the `security-extended` suite.

View File

@@ -26,10 +26,23 @@ string permissionsForJob(Job job) {
"{" + concat(string permission | permission = jobNeedsPermission(job) | permission, ", ") + "}"
}
predicate jobHasPermissions(Job job) {
exists(job.getPermissions())
or
exists(job.getEnclosingWorkflow().getPermissions())
or
// The workflow is reusable and cannot be triggered in any other way; check callers
exists(ReusableWorkflow r | r = job.getEnclosingWorkflow() |
not exists(Event e | e = r.getOn().getAnEvent() | e.getName() != "workflow_call") and
forall(Job caller | caller = job.getEnclosingWorkflow().(ReusableWorkflow).getACaller() |
jobHasPermissions(caller)
)
)
}
from Job job, string permissions
where
not exists(job.getPermissions()) and
not exists(job.getEnclosingWorkflow().getPermissions()) and
not jobHasPermissions(job) and
// exists a trigger event that is not a workflow_call
exists(Event e |
e = job.getATriggerEvent() and

View File

@@ -20,6 +20,6 @@ from ArtifactPoisoningFlow::PathNode source, ArtifactPoisoningFlow::PathNode sin
where
ArtifactPoisoningFlow::flowPath(source, sink) and
event = getRelevantEventInPrivilegedContext(sink.getNode())
select sink.getNode(), source, sink,
"Potential artifact poisoning in $@, which may be controlled by an external user ($@).", sink,
sink.getNode().toString(), event, event.getName()
select source.getNode(), source, sink,
"Potential artifact poisoning; the artifact being consumed has contents that may be controlled by an external user ($@).",
event, event.getName()

View File

@@ -20,6 +20,5 @@ from ArtifactPoisoningFlow::PathNode source, ArtifactPoisoningFlow::PathNode sin
where
ArtifactPoisoningFlow::flowPath(source, sink) and
inNonPrivilegedContext(sink.getNode().asExpr())
select sink.getNode(), source, sink,
"Potential artifact poisoning in $@, which may be controlled by an external user.", sink,
sink.getNode().toString()
select source.getNode(), source, sink,
"Potential artifact poisoning; the artifact being consumed has contents that may be controlled by an external user."

View File

@@ -0,0 +1,9 @@
## 0.6.26
### Major Analysis Improvements
* Fixed alert messages in `actions/artifact-poisoning/critical` and `actions/artifact-poisoning/medium` as they previously included a redundant placeholder in the alert message that would on occasion contain a long block of yml that makes the alert difficult to understand. Also improved the wording to make it clearer that it is not the artifact that is being poisoned, but instead a potentially untrusted artifact that is consumed. Finally, changed the alert location to be the source, to align more with other queries reporting an artifact (e.g. zipslip) which is more useful.
### Minor Analysis Improvements
* The query `actions/missing-workflow-permissions` no longer produces false positive results on reusable workflows where all callers set permissions.

View File

@@ -1,2 +1,2 @@
---
lastReleaseVersion: 0.6.25
lastReleaseVersion: 0.6.26

View File

@@ -1,5 +1,5 @@
name: codeql/actions-queries
version: 0.6.25
version: 0.6.27-dev
library: false
warnOnImplicitThis: true
groups: [actions, queries]

View File

@@ -0,0 +1,9 @@
on:
workflow_call:
jobs:
build:
name: Build and test
runs-on: ubuntu-latest
steps:
- uses: actions/deploy-pages

View File

@@ -0,0 +1,11 @@
on:
workflow_dispatch:
permissions:
contents: read
id-token: write
pages: write
jobs:
call-workflow:
uses: ./.github/workflows/perms11.yml

View File

@@ -55,21 +55,21 @@ nodes
| .github/workflows/test25.yml:39:14:40:45 | ./gradlew buildScanPublishPrevious\n | semmle.label | ./gradlew buildScanPublishPrevious\n |
subpaths
#select
| .github/workflows/artifactpoisoning11.yml:38:11:38:77 | ./sonarcloud-data/x.py build -j$(nproc) --compiler gcc --skip-build | .github/workflows/artifactpoisoning11.yml:13:9:32:6 | Uses Step | .github/workflows/artifactpoisoning11.yml:38:11:38:77 | ./sonarcloud-data/x.py build -j$(nproc) --compiler gcc --skip-build | Potential artifact poisoning in $@, which may be controlled by an external user ($@). | .github/workflows/artifactpoisoning11.yml:38:11:38:77 | ./sonarcloud-data/x.py build -j$(nproc) --compiler gcc --skip-build | ./sonarcloud-data/x.py build -j$(nproc) --compiler gcc --skip-build | .github/workflows/artifactpoisoning11.yml:4:3:4:14 | workflow_run | workflow_run |
| .github/workflows/artifactpoisoning12.yml:38:11:38:25 | python foo/x.py | .github/workflows/artifactpoisoning12.yml:13:9:32:6 | Uses Step | .github/workflows/artifactpoisoning12.yml:38:11:38:25 | python foo/x.py | Potential artifact poisoning in $@, which may be controlled by an external user ($@). | .github/workflows/artifactpoisoning12.yml:38:11:38:25 | python foo/x.py | python foo/x.py | .github/workflows/artifactpoisoning12.yml:4:3:4:14 | workflow_run | workflow_run |
| .github/workflows/artifactpoisoning21.yml:19:14:20:21 | sh foo/cmd\n | .github/workflows/artifactpoisoning21.yml:13:9:18:6 | Uses Step | .github/workflows/artifactpoisoning21.yml:19:14:20:21 | sh foo/cmd\n | Potential artifact poisoning in $@, which may be controlled by an external user ($@). | .github/workflows/artifactpoisoning21.yml:19:14:20:21 | sh foo/cmd\n | sh foo/cmd\n | .github/workflows/artifactpoisoning21.yml:4:3:4:14 | workflow_run | workflow_run |
| .github/workflows/artifactpoisoning22.yml:18:14:18:19 | sh cmd | .github/workflows/artifactpoisoning22.yml:13:9:17:6 | Uses Step | .github/workflows/artifactpoisoning22.yml:18:14:18:19 | sh cmd | Potential artifact poisoning in $@, which may be controlled by an external user ($@). | .github/workflows/artifactpoisoning22.yml:18:14:18:19 | sh cmd | sh cmd | .github/workflows/artifactpoisoning22.yml:4:3:4:14 | workflow_run | workflow_run |
| .github/workflows/artifactpoisoning31.yml:19:14:19:22 | ./foo/cmd | .github/workflows/artifactpoisoning31.yml:13:9:15:6 | Run Step | .github/workflows/artifactpoisoning31.yml:19:14:19:22 | ./foo/cmd | Potential artifact poisoning in $@, which may be controlled by an external user ($@). | .github/workflows/artifactpoisoning31.yml:19:14:19:22 | ./foo/cmd | ./foo/cmd | .github/workflows/artifactpoisoning31.yml:4:3:4:14 | workflow_run | workflow_run |
| .github/workflows/artifactpoisoning32.yml:17:14:18:20 | ./bar/cmd\n | .github/workflows/artifactpoisoning32.yml:13:9:16:6 | Run Step | .github/workflows/artifactpoisoning32.yml:17:14:18:20 | ./bar/cmd\n | Potential artifact poisoning in $@, which may be controlled by an external user ($@). | .github/workflows/artifactpoisoning32.yml:17:14:18:20 | ./bar/cmd\n | ./bar/cmd\n | .github/workflows/artifactpoisoning32.yml:4:3:4:14 | workflow_run | workflow_run |
| .github/workflows/artifactpoisoning33.yml:17:14:18:20 | ./bar/cmd\n | .github/workflows/artifactpoisoning33.yml:13:9:16:6 | Run Step | .github/workflows/artifactpoisoning33.yml:17:14:18:20 | ./bar/cmd\n | Potential artifact poisoning in $@, which may be controlled by an external user ($@). | .github/workflows/artifactpoisoning33.yml:17:14:18:20 | ./bar/cmd\n | ./bar/cmd\n | .github/workflows/artifactpoisoning33.yml:4:3:4:14 | workflow_run | workflow_run |
| .github/workflows/artifactpoisoning34.yml:20:14:22:23 | npm install\nnpm run lint\n | .github/workflows/artifactpoisoning34.yml:13:9:16:6 | Run Step | .github/workflows/artifactpoisoning34.yml:20:14:22:23 | npm install\nnpm run lint\n | Potential artifact poisoning in $@, which may be controlled by an external user ($@). | .github/workflows/artifactpoisoning34.yml:20:14:22:23 | npm install\nnpm run lint\n | npm install\nnpm run lint\n | .github/workflows/artifactpoisoning34.yml:4:3:4:14 | workflow_run | workflow_run |
| .github/workflows/artifactpoisoning41.yml:22:14:22:22 | ./foo/cmd | .github/workflows/artifactpoisoning41.yml:13:9:21:6 | Run Step | .github/workflows/artifactpoisoning41.yml:22:14:22:22 | ./foo/cmd | Potential artifact poisoning in $@, which may be controlled by an external user ($@). | .github/workflows/artifactpoisoning41.yml:22:14:22:22 | ./foo/cmd | ./foo/cmd | .github/workflows/artifactpoisoning41.yml:4:3:4:14 | workflow_run | workflow_run |
| .github/workflows/artifactpoisoning42.yml:22:14:22:18 | ./cmd | .github/workflows/artifactpoisoning42.yml:13:9:21:6 | Run Step | .github/workflows/artifactpoisoning42.yml:22:14:22:18 | ./cmd | Potential artifact poisoning in $@, which may be controlled by an external user ($@). | .github/workflows/artifactpoisoning42.yml:22:14:22:18 | ./cmd | ./cmd | .github/workflows/artifactpoisoning42.yml:4:3:4:14 | workflow_run | workflow_run |
| .github/workflows/artifactpoisoning71.yml:17:14:18:40 | sed -f config foo.md > bar.md\n | .github/workflows/artifactpoisoning71.yml:9:9:16:6 | Uses Step | .github/workflows/artifactpoisoning71.yml:17:14:18:40 | sed -f config foo.md > bar.md\n | Potential artifact poisoning in $@, which may be controlled by an external user ($@). | .github/workflows/artifactpoisoning71.yml:17:14:18:40 | sed -f config foo.md > bar.md\n | sed -f config foo.md > bar.md\n | .github/workflows/artifactpoisoning71.yml:4:5:4:16 | workflow_run | workflow_run |
| .github/workflows/artifactpoisoning81.yml:31:14:31:27 | python test.py | .github/workflows/artifactpoisoning81.yml:28:9:31:6 | Uses Step | .github/workflows/artifactpoisoning81.yml:31:14:31:27 | python test.py | Potential artifact poisoning in $@, which may be controlled by an external user ($@). | .github/workflows/artifactpoisoning81.yml:31:14:31:27 | python test.py | python test.py | .github/workflows/artifactpoisoning81.yml:3:5:3:23 | pull_request_target | pull_request_target |
| .github/workflows/artifactpoisoning92.yml:28:9:29:6 | Uses Step | .github/actions/download-artifact-2/action.yaml:6:7:25:4 | Uses Step | .github/workflows/artifactpoisoning92.yml:28:9:29:6 | Uses Step | Potential artifact poisoning in $@, which may be controlled by an external user ($@). | .github/workflows/artifactpoisoning92.yml:28:9:29:6 | Uses Step | Uses Step | .github/workflows/artifactpoisoning92.yml:3:3:3:14 | workflow_run | workflow_run |
| .github/workflows/artifactpoisoning92.yml:29:14:29:26 | make snapshot | .github/actions/download-artifact-2/action.yaml:6:7:25:4 | Uses Step | .github/workflows/artifactpoisoning92.yml:29:14:29:26 | make snapshot | Potential artifact poisoning in $@, which may be controlled by an external user ($@). | .github/workflows/artifactpoisoning92.yml:29:14:29:26 | make snapshot | make snapshot | .github/workflows/artifactpoisoning92.yml:3:3:3:14 | workflow_run | workflow_run |
| .github/workflows/artifactpoisoning96.yml:18:14:18:24 | npm install | .github/workflows/artifactpoisoning96.yml:13:9:18:6 | Uses Step | .github/workflows/artifactpoisoning96.yml:18:14:18:24 | npm install | Potential artifact poisoning in $@, which may be controlled by an external user ($@). | .github/workflows/artifactpoisoning96.yml:18:14:18:24 | npm install | npm install | .github/workflows/artifactpoisoning96.yml:2:3:2:14 | workflow_run | workflow_run |
| .github/workflows/artifactpoisoning101.yml:17:14:19:59 | PR_NUMBER=$(./get_pull_request_number.sh pr_number.txt)\necho "PR_NUMBER=$PR_NUMBER" >> $GITHUB_OUTPUT \n | .github/workflows/artifactpoisoning101.yml:10:9:16:6 | Uses Step | .github/workflows/artifactpoisoning101.yml:17:14:19:59 | PR_NUMBER=$(./get_pull_request_number.sh pr_number.txt)\necho "PR_NUMBER=$PR_NUMBER" >> $GITHUB_OUTPUT \n | Potential artifact poisoning in $@, which may be controlled by an external user ($@). | .github/workflows/artifactpoisoning101.yml:17:14:19:59 | PR_NUMBER=$(./get_pull_request_number.sh pr_number.txt)\necho "PR_NUMBER=$PR_NUMBER" >> $GITHUB_OUTPUT \n | PR_NUMBER=$(./get_pull_request_number.sh pr_number.txt)\necho "PR_NUMBER=$PR_NUMBER" >> $GITHUB_OUTPUT \n | .github/workflows/artifactpoisoning101.yml:4:3:4:21 | pull_request_target | pull_request_target |
| .github/workflows/test18.yml:36:15:40:58 | Uses Step | .github/workflows/test18.yml:12:15:33:12 | Uses Step | .github/workflows/test18.yml:36:15:40:58 | Uses Step | Potential artifact poisoning in $@, which may be controlled by an external user ($@). | .github/workflows/test18.yml:36:15:40:58 | Uses Step | Uses Step | .github/workflows/test18.yml:3:5:3:16 | workflow_run | workflow_run |
| .github/workflows/test25.yml:39:14:40:45 | ./gradlew buildScanPublishPrevious\n | .github/workflows/test25.yml:22:9:32:6 | Uses Step: downloadBuildScan | .github/workflows/test25.yml:39:14:40:45 | ./gradlew buildScanPublishPrevious\n | Potential artifact poisoning in $@, which may be controlled by an external user ($@). | .github/workflows/test25.yml:39:14:40:45 | ./gradlew buildScanPublishPrevious\n | ./gradlew buildScanPublishPrevious\n | .github/workflows/test25.yml:2:3:2:14 | workflow_run | workflow_run |
| .github/actions/download-artifact-2/action.yaml:6:7:25:4 | Uses Step | .github/actions/download-artifact-2/action.yaml:6:7:25:4 | Uses Step | .github/workflows/artifactpoisoning92.yml:28:9:29:6 | Uses Step | Potential artifact poisoning; the artifact being consumed has contents that may be controlled by an external user ($@). | .github/workflows/artifactpoisoning92.yml:3:3:3:14 | workflow_run | workflow_run |
| .github/actions/download-artifact-2/action.yaml:6:7:25:4 | Uses Step | .github/actions/download-artifact-2/action.yaml:6:7:25:4 | Uses Step | .github/workflows/artifactpoisoning92.yml:29:14:29:26 | make snapshot | Potential artifact poisoning; the artifact being consumed has contents that may be controlled by an external user ($@). | .github/workflows/artifactpoisoning92.yml:3:3:3:14 | workflow_run | workflow_run |
| .github/workflows/artifactpoisoning11.yml:13:9:32:6 | Uses Step | .github/workflows/artifactpoisoning11.yml:13:9:32:6 | Uses Step | .github/workflows/artifactpoisoning11.yml:38:11:38:77 | ./sonarcloud-data/x.py build -j$(nproc) --compiler gcc --skip-build | Potential artifact poisoning; the artifact being consumed has contents that may be controlled by an external user ($@). | .github/workflows/artifactpoisoning11.yml:4:3:4:14 | workflow_run | workflow_run |
| .github/workflows/artifactpoisoning12.yml:13:9:32:6 | Uses Step | .github/workflows/artifactpoisoning12.yml:13:9:32:6 | Uses Step | .github/workflows/artifactpoisoning12.yml:38:11:38:25 | python foo/x.py | Potential artifact poisoning; the artifact being consumed has contents that may be controlled by an external user ($@). | .github/workflows/artifactpoisoning12.yml:4:3:4:14 | workflow_run | workflow_run |
| .github/workflows/artifactpoisoning21.yml:13:9:18:6 | Uses Step | .github/workflows/artifactpoisoning21.yml:13:9:18:6 | Uses Step | .github/workflows/artifactpoisoning21.yml:19:14:20:21 | sh foo/cmd\n | Potential artifact poisoning; the artifact being consumed has contents that may be controlled by an external user ($@). | .github/workflows/artifactpoisoning21.yml:4:3:4:14 | workflow_run | workflow_run |
| .github/workflows/artifactpoisoning22.yml:13:9:17:6 | Uses Step | .github/workflows/artifactpoisoning22.yml:13:9:17:6 | Uses Step | .github/workflows/artifactpoisoning22.yml:18:14:18:19 | sh cmd | Potential artifact poisoning; the artifact being consumed has contents that may be controlled by an external user ($@). | .github/workflows/artifactpoisoning22.yml:4:3:4:14 | workflow_run | workflow_run |
| .github/workflows/artifactpoisoning31.yml:13:9:15:6 | Run Step | .github/workflows/artifactpoisoning31.yml:13:9:15:6 | Run Step | .github/workflows/artifactpoisoning31.yml:19:14:19:22 | ./foo/cmd | Potential artifact poisoning; the artifact being consumed has contents that may be controlled by an external user ($@). | .github/workflows/artifactpoisoning31.yml:4:3:4:14 | workflow_run | workflow_run |
| .github/workflows/artifactpoisoning32.yml:13:9:16:6 | Run Step | .github/workflows/artifactpoisoning32.yml:13:9:16:6 | Run Step | .github/workflows/artifactpoisoning32.yml:17:14:18:20 | ./bar/cmd\n | Potential artifact poisoning; the artifact being consumed has contents that may be controlled by an external user ($@). | .github/workflows/artifactpoisoning32.yml:4:3:4:14 | workflow_run | workflow_run |
| .github/workflows/artifactpoisoning33.yml:13:9:16:6 | Run Step | .github/workflows/artifactpoisoning33.yml:13:9:16:6 | Run Step | .github/workflows/artifactpoisoning33.yml:17:14:18:20 | ./bar/cmd\n | Potential artifact poisoning; the artifact being consumed has contents that may be controlled by an external user ($@). | .github/workflows/artifactpoisoning33.yml:4:3:4:14 | workflow_run | workflow_run |
| .github/workflows/artifactpoisoning34.yml:13:9:16:6 | Run Step | .github/workflows/artifactpoisoning34.yml:13:9:16:6 | Run Step | .github/workflows/artifactpoisoning34.yml:20:14:22:23 | npm install\nnpm run lint\n | Potential artifact poisoning; the artifact being consumed has contents that may be controlled by an external user ($@). | .github/workflows/artifactpoisoning34.yml:4:3:4:14 | workflow_run | workflow_run |
| .github/workflows/artifactpoisoning41.yml:13:9:21:6 | Run Step | .github/workflows/artifactpoisoning41.yml:13:9:21:6 | Run Step | .github/workflows/artifactpoisoning41.yml:22:14:22:22 | ./foo/cmd | Potential artifact poisoning; the artifact being consumed has contents that may be controlled by an external user ($@). | .github/workflows/artifactpoisoning41.yml:4:3:4:14 | workflow_run | workflow_run |
| .github/workflows/artifactpoisoning42.yml:13:9:21:6 | Run Step | .github/workflows/artifactpoisoning42.yml:13:9:21:6 | Run Step | .github/workflows/artifactpoisoning42.yml:22:14:22:18 | ./cmd | Potential artifact poisoning; the artifact being consumed has contents that may be controlled by an external user ($@). | .github/workflows/artifactpoisoning42.yml:4:3:4:14 | workflow_run | workflow_run |
| .github/workflows/artifactpoisoning71.yml:9:9:16:6 | Uses Step | .github/workflows/artifactpoisoning71.yml:9:9:16:6 | Uses Step | .github/workflows/artifactpoisoning71.yml:17:14:18:40 | sed -f config foo.md > bar.md\n | Potential artifact poisoning; the artifact being consumed has contents that may be controlled by an external user ($@). | .github/workflows/artifactpoisoning71.yml:4:5:4:16 | workflow_run | workflow_run |
| .github/workflows/artifactpoisoning81.yml:28:9:31:6 | Uses Step | .github/workflows/artifactpoisoning81.yml:28:9:31:6 | Uses Step | .github/workflows/artifactpoisoning81.yml:31:14:31:27 | python test.py | Potential artifact poisoning; the artifact being consumed has contents that may be controlled by an external user ($@). | .github/workflows/artifactpoisoning81.yml:3:5:3:23 | pull_request_target | pull_request_target |
| .github/workflows/artifactpoisoning96.yml:13:9:18:6 | Uses Step | .github/workflows/artifactpoisoning96.yml:13:9:18:6 | Uses Step | .github/workflows/artifactpoisoning96.yml:18:14:18:24 | npm install | Potential artifact poisoning; the artifact being consumed has contents that may be controlled by an external user ($@). | .github/workflows/artifactpoisoning96.yml:2:3:2:14 | workflow_run | workflow_run |
| .github/workflows/artifactpoisoning101.yml:10:9:16:6 | Uses Step | .github/workflows/artifactpoisoning101.yml:10:9:16:6 | Uses Step | .github/workflows/artifactpoisoning101.yml:17:14:19:59 | PR_NUMBER=$(./get_pull_request_number.sh pr_number.txt)\necho "PR_NUMBER=$PR_NUMBER" >> $GITHUB_OUTPUT \n | Potential artifact poisoning; the artifact being consumed has contents that may be controlled by an external user ($@). | .github/workflows/artifactpoisoning101.yml:4:3:4:21 | pull_request_target | pull_request_target |
| .github/workflows/test18.yml:12:15:33:12 | Uses Step | .github/workflows/test18.yml:12:15:33:12 | Uses Step | .github/workflows/test18.yml:36:15:40:58 | Uses Step | Potential artifact poisoning; the artifact being consumed has contents that may be controlled by an external user ($@). | .github/workflows/test18.yml:3:5:3:16 | workflow_run | workflow_run |
| .github/workflows/test25.yml:22:9:32:6 | Uses Step: downloadBuildScan | .github/workflows/test25.yml:22:9:32:6 | Uses Step: downloadBuildScan | .github/workflows/test25.yml:39:14:40:45 | ./gradlew buildScanPublishPrevious\n | Potential artifact poisoning; the artifact being consumed has contents that may be controlled by an external user ($@). | .github/workflows/test25.yml:2:3:2:14 | workflow_run | workflow_run |

View File

@@ -7,10 +7,12 @@ ql/cpp/ql/src/Diagnostics/ExtractedFiles.ql
ql/cpp/ql/src/Diagnostics/ExtractionWarnings.ql
ql/cpp/ql/src/Diagnostics/FailedExtractorInvocations.ql
ql/cpp/ql/src/Likely Bugs/Arithmetic/BadAdditionOverflowCheck.ql
ql/cpp/ql/src/Likely Bugs/Arithmetic/IntMultToLong.ql
ql/cpp/ql/src/Likely Bugs/Arithmetic/SignedOverflowCheck.ql
ql/cpp/ql/src/Likely Bugs/Conversion/CastArrayPointerArithmetic.ql
ql/cpp/ql/src/Likely Bugs/Format/SnprintfOverflow.ql
ql/cpp/ql/src/Likely Bugs/Format/WrongNumberOfFormatArguments.ql
ql/cpp/ql/src/Likely Bugs/Format/WrongTypeFormatArguments.ql
ql/cpp/ql/src/Likely Bugs/Memory Management/AllocaInLoop.ql
ql/cpp/ql/src/Likely Bugs/Memory Management/PointerOverflow.ql
ql/cpp/ql/src/Likely Bugs/Memory Management/ReturnStackAllocatedMemory.ql
@@ -28,6 +30,7 @@ ql/cpp/ql/src/Security/CWE/CWE-120/VeryLikelyOverrunWrite.ql
ql/cpp/ql/src/Security/CWE/CWE-131/NoSpaceForZeroTerminator.ql
ql/cpp/ql/src/Security/CWE/CWE-134/UncontrolledFormatString.ql
ql/cpp/ql/src/Security/CWE/CWE-190/ArithmeticUncontrolled.ql
ql/cpp/ql/src/Security/CWE/CWE-190/ComparisonWithWiderType.ql
ql/cpp/ql/src/Security/CWE/CWE-191/UnsignedDifferenceExpressionComparedZero.ql
ql/cpp/ql/src/Security/CWE/CWE-253/HResultBooleanConversion.ql
ql/cpp/ql/src/Security/CWE/CWE-311/CleartextFileWrite.ql
@@ -40,6 +43,7 @@ ql/cpp/ql/src/Security/CWE/CWE-367/TOCTOUFilesystemRace.ql
ql/cpp/ql/src/Security/CWE/CWE-416/IteratorToExpiredContainer.ql
ql/cpp/ql/src/Security/CWE/CWE-416/UseOfStringAfterLifetimeEnds.ql
ql/cpp/ql/src/Security/CWE/CWE-416/UseOfUniquePointerAfterLifetimeEnds.ql
ql/cpp/ql/src/Security/CWE/CWE-468/SuspiciousAddWithSizeof.ql
ql/cpp/ql/src/Security/CWE/CWE-497/ExposedSystemData.ql
ql/cpp/ql/src/Security/CWE/CWE-611/XXE.ql
ql/cpp/ql/src/Security/CWE/CWE-676/DangerousFunctionOverflow.ql

View File

@@ -1,3 +1,14 @@
## 10.0.0
### Breaking Changes
* The deprecated `NonThrowingFunction` class has been removed, use `NonCppThrowingFunction` instead.
* The deprecated `ThrowingFunction` class has been removed, use `AlwaysSehThrowingFunction` instead.
### New Features
* Added a subclass `AutoconfConfigureTestFile` of `ConfigurationTestFile` that represents files created by GNU autoconf configure scripts to test the build configuration.
## 9.0.0
### Breaking Changes

View File

@@ -0,0 +1,4 @@
---
category: feature
---
* Data flow barriers and barrier guards can now be added using data extensions. For more information see [Customizing library models for C and C++](https://codeql.github.com/docs/codeql-language-guides/customizing-library-models-for-cpp/).

View File

@@ -0,0 +1,10 @@
## 10.0.0
### Breaking Changes
* The deprecated `NonThrowingFunction` class has been removed, use `NonCppThrowingFunction` instead.
* The deprecated `ThrowingFunction` class has been removed, use `AlwaysSehThrowingFunction` instead.
### New Features
* Added a subclass `AutoconfConfigureTestFile` of `ConfigurationTestFile` that represents files created by GNU autoconf configure scripts to test the build configuration.

View File

@@ -1,2 +1,2 @@
---
lastReleaseVersion: 9.0.0
lastReleaseVersion: 10.0.0

View File

@@ -12,4 +12,7 @@ extensions:
- ["", "", False, "_malloca", "0", "", "", False]
- ["", "", False, "calloc", "1", "0", "", True]
- ["std", "", False, "calloc", "1", "0", "", True]
- ["bsl", "", False, "calloc", "1", "0", "", True]
- ["bsl", "", False, "calloc", "1", "0", "", True]
- ["", "", False, "aligned_alloc", "1", "", "", True]
- ["std", "", False, "aligned_alloc", "1", "", "", True]
- ["bsl", "", False, "aligned_alloc", "1", "", "", True]

View File

@@ -1,5 +1,5 @@
name: codeql/cpp-all
version: 9.0.0
version: 10.0.1-dev
groups: cpp
dbscheme: semmlecode.cpp.dbscheme
extractor: cpp

View File

@@ -42,3 +42,10 @@ class MesonPrivateTestFile extends ConfigurationTestFile {
)
}
}
/**
* A file created by a GNU autoconf configure script to test the system configuration.
*/
class AutoconfConfigureTestFile extends ConfigurationTestFile {
AutoconfConfigureTestFile() { this.getBaseName().regexpMatch("conftest[0-9]*\\.c(pp)?") }
}

View File

@@ -459,6 +459,13 @@ class FormatLiteral extends Literal instanceof StringLiteral {
*/
int getConvSpecOffset(int n) { result = this.getFormat().indexOf("%", n, 0) }
/**
* Gets the nth conversion specifier string.
*/
private string getConvSpecString(int n) {
n >= 0 and result = "%" + this.getFormat().splitAt("%", n + 1)
}
/*
* Each of these predicates gets a regular expressions to match each individual
* parts of a conversion specifier.
@@ -524,22 +531,20 @@ class FormatLiteral extends Literal instanceof StringLiteral {
int n, string spec, string params, string flags, string width, string prec, string len,
string conv
) {
exists(int offset, string fmt, string rst, string regexp |
offset = this.getConvSpecOffset(n) and
fmt = this.getFormat() and
rst = fmt.substring(offset, fmt.length()) and
exists(string convSpec, string regexp |
convSpec = this.getConvSpecString(n) and
regexp = this.getConvSpecRegexp() and
(
spec = rst.regexpCapture(regexp, 1) and
params = rst.regexpCapture(regexp, 2) and
flags = rst.regexpCapture(regexp, 3) and
width = rst.regexpCapture(regexp, 4) and
prec = rst.regexpCapture(regexp, 5) and
len = rst.regexpCapture(regexp, 6) and
conv = rst.regexpCapture(regexp, 7)
spec = convSpec.regexpCapture(regexp, 1) and
params = convSpec.regexpCapture(regexp, 2) and
flags = convSpec.regexpCapture(regexp, 3) and
width = convSpec.regexpCapture(regexp, 4) and
prec = convSpec.regexpCapture(regexp, 5) and
len = convSpec.regexpCapture(regexp, 6) and
conv = convSpec.regexpCapture(regexp, 7)
or
spec = rst.regexpCapture(regexp, 1) and
not exists(rst.regexpCapture(regexp, 2)) and
spec = convSpec.regexpCapture(regexp, 1) and
not exists(convSpec.regexpCapture(regexp, 2)) and
params = "" and
flags = "" and
width = "" and
@@ -554,12 +559,10 @@ class FormatLiteral extends Literal instanceof StringLiteral {
* Gets the nth conversion specifier (including the initial `%`).
*/
string getConvSpec(int n) {
exists(int offset, string fmt, string rst, string regexp |
offset = this.getConvSpecOffset(n) and
fmt = this.getFormat() and
rst = fmt.substring(offset, fmt.length()) and
exists(string convSpec, string regexp |
convSpec = this.getConvSpecString(n) and
regexp = this.getConvSpecRegexp() and
result = rst.regexpCapture(regexp, 1)
result = convSpec.regexpCapture(regexp, 1)
)
}

View File

@@ -194,6 +194,13 @@ class ScanfFormatLiteral extends Expr {
)
}
/**
* Gets the nth conversion specifier string.
*/
private string getConvSpecString(int n) {
n >= 0 and result = "%" + this.getFormat().splitAt("%", n + 1)
}
/**
* Gets the regular expression to match each individual part of a conversion specifier.
*/
@@ -227,16 +234,14 @@ class ScanfFormatLiteral extends Expr {
* specifier.
*/
predicate parseConvSpec(int n, string spec, string width, string len, string conv) {
exists(int offset, string fmt, string rst, string regexp |
offset = this.getConvSpecOffset(n) and
fmt = this.getFormat() and
rst = fmt.substring(offset, fmt.length()) and
exists(string convSpec, string regexp |
convSpec = this.getConvSpecString(n) and
regexp = this.getConvSpecRegexp() and
(
spec = rst.regexpCapture(regexp, 1) and
width = rst.regexpCapture(regexp, 2) and
len = rst.regexpCapture(regexp, 3) and
conv = rst.regexpCapture(regexp, 4)
spec = convSpec.regexpCapture(regexp, 1) and
width = convSpec.regexpCapture(regexp, 2) and
len = convSpec.regexpCapture(regexp, 3) and
conv = convSpec.regexpCapture(regexp, 4)
)
)
}

View File

@@ -6,11 +6,15 @@
*
* The extensible relations have the following columns:
* - Sources:
* `namespace; type; subtypes; name; signature; ext; output; kind`
* `namespace; type; subtypes; name; signature; ext; output; kind; provenance`
* - Sinks:
* `namespace; type; subtypes; name; signature; ext; input; kind`
* `namespace; type; subtypes; name; signature; ext; input; kind; provenance`
* - Summaries:
* `namespace; type; subtypes; name; signature; ext; input; output; kind`
* `namespace; type; subtypes; name; signature; ext; input; output; kind; provenance`
* - Barriers:
* `namespace; type; subtypes; name; signature; ext; output; kind; provenance`
* - BarrierGuards:
* `namespace; type; subtypes; name; signature; ext; input; acceptingValue; kind; provenance`
*
* The interpretation of a row is similar to API-graphs with a left-to-right
* reading.
@@ -87,11 +91,23 @@
* value, and
* - flow from the _second_ indirection of the 0th argument to the first
* indirection of the return value, etc.
* 8. The `kind` column is a tag that can be referenced from QL to determine to
* 8. The `acceptingValue` column of barrier guard models specifies the condition
* under which the guard blocks flow. It can be one of "true" or "false". In
* the future "no-exception", "not-zero", "null", "not-null" may be supported.
* 9. The `kind` column is a tag that can be referenced from QL to determine to
* which classes the interpreted elements should be added. For example, for
* sources "remote" indicates a default remote flow source, and for summaries
* "taint" indicates a default additional taint step and "value" indicates a
* globally applicable value-preserving step.
* 10. The `provenance` column is a tag to indicate the origin and verification of a model.
* The format is {origin}-{verification} or just "manual" where the origin describes
* the origin of the model and verification describes how the model has been verified.
* Some examples are:
* - "df-generated": The model has been generated by the model generator tool.
* - "df-manual": The model has been generated by the model generator and verified by a human.
* - "manual": The model has been written by hand.
* This information is used in a heuristic for dataflow analysis to determine, if a
* model or source code should be used for determining flow.
*/
import cpp
@@ -931,13 +947,13 @@ private module Cached {
private predicate barrierGuardChecks(IRGuardCondition g, Expr e, boolean gv, TKindModelPair kmp) {
exists(
SourceSinkInterpretationInput::InterpretNode n, Public::AcceptingValue acceptingvalue,
SourceSinkInterpretationInput::InterpretNode n, Public::AcceptingValue acceptingValue,
string kind, string model
|
isBarrierGuardNode(n, acceptingvalue, kind, model) and
isBarrierGuardNode(n, acceptingValue, kind, model) and
n.asNode().asExpr() = e and
kmp = TMkPair(kind, model) and
gv = convertAcceptingValue(acceptingvalue).asBooleanValue() and
gv = convertAcceptingValue(acceptingValue).asBooleanValue() and
n.asNode().(Private::ArgumentNode).getCall().asCallInstruction() = g
)
}
@@ -954,14 +970,14 @@ private module Cached {
) {
exists(
SourceSinkInterpretationInput::InterpretNode interpretNode,
Public::AcceptingValue acceptingvalue, string kind, string model, int indirectionIndex,
Public::AcceptingValue acceptingValue, string kind, string model, int indirectionIndex,
Private::ArgumentNode arg
|
isBarrierGuardNode(interpretNode, acceptingvalue, kind, model) and
isBarrierGuardNode(interpretNode, acceptingValue, kind, model) and
arg = interpretNode.asNode() and
arg.asIndirectExpr(indirectionIndex) = e and
kmp = MkKindModelPairIntPair(TMkPair(kind, model), indirectionIndex) and
gv = convertAcceptingValue(acceptingvalue).asBooleanValue() and
gv = convertAcceptingValue(acceptingValue).asBooleanValue() and
arg.getCall().asCallInstruction() = g
)
}

View File

@@ -33,7 +33,7 @@ extensible predicate barrierModel(
*/
extensible predicate barrierGuardModel(
string namespace, string type, boolean subtypes, string name, string signature, string ext,
string input, string acceptingvalue, string kind, string provenance, QlBuiltins::ExtensionId madId
string input, string acceptingValue, string kind, string provenance, QlBuiltins::ExtensionId madId
);
/**

View File

@@ -162,13 +162,13 @@ module SourceSinkInterpretationInput implements
}
predicate barrierGuardElement(
Element e, string input, Public::AcceptingValue acceptingvalue, string kind,
Element e, string input, Public::AcceptingValue acceptingValue, string kind,
Public::Provenance provenance, string model
) {
exists(
string package, string type, boolean subtypes, string name, string signature, string ext
|
barrierGuardModel(package, type, subtypes, name, signature, ext, input, acceptingvalue, kind,
barrierGuardModel(package, type, subtypes, name, signature, ext, input, acceptingValue, kind,
provenance, model) and
e = interpretElement(package, type, subtypes, name, signature, ext)
)

View File

@@ -11,10 +11,3 @@ import semmle.code.cpp.models.Models
* The function may still raise a structured exception handling (SEH) exception.
*/
abstract class NonCppThrowingFunction extends Function { }
/**
* A function that is guaranteed to never throw.
*
* DEPRECATED: use `NonCppThrowingFunction` instead.
*/
deprecated class NonThrowingFunction = NonCppThrowingFunction;

View File

@@ -10,19 +10,6 @@ import semmle.code.cpp.Function
import semmle.code.cpp.models.Models
import semmle.code.cpp.models.interfaces.FunctionInputsAndOutputs
/**
* A function that is known to raise an exception.
*
* DEPRECATED: use `AlwaysSehThrowingFunction` instead.
*/
abstract deprecated class ThrowingFunction extends Function {
/**
* Holds if this function may throw an exception during evaluation.
* If `unconditional` is `true` the function always throws an exception.
*/
abstract predicate mayThrowException(boolean unconditional);
}
/**
* A function that unconditionally raises a structured exception handling (SEH) exception.
*/

View File

@@ -1,3 +1,14 @@
## 1.6.1
### Minor Analysis Improvements
* Added `AllocationFunction` models for `aligned_alloc`, `std::aligned_alloc`, and `bsl::aligned_alloc`.
* The "Comparison of narrow type with wide type in loop condition" (`cpp/comparison-with-wider-type`) query has been upgraded to `high` precision. This query will now run in the default code scanning suite.
* The "Multiplication result converted to larger type" (`cpp/integer-multiplication-cast-to-long`) query has been upgraded to `high` precision. This query will now run in the default code scanning suite.
* The "Suspicious add with sizeof" (`cpp/suspicious-add-sizeof`) query has been upgraded to `high` precision. This query will now run in the default code scanning suite.
* The "Wrong type of arguments to formatting function" (`cpp/wrong-type-format-argument`) query has been upgraded to `high` precision. This query will now run in the default code scanning suite.
* The "Implicit function declaration" (`cpp/implicit-function-declaration`) query has been upgraded to `high` precision. However, for `build mode: none` databases, it no longer produces any results. The results in this mode were found to be very noisy and fundamentally imprecise.
## 1.6.0
### Query Metadata Changes
@@ -355,7 +366,7 @@ No user-facing changes.
### Minor Analysis Improvements
* The "non-constant format string" query (`cpp/non-constant-format`) has been updated to produce fewer false positives.
* Added dataflow models for the `gettext` function variants.
* Added dataflow models for the `gettext` function variants.
## 0.9.4

View File

@@ -5,7 +5,7 @@
* @kind problem
* @problem.severity warning
* @security-severity 8.1
* @precision medium
* @precision high
* @id cpp/integer-multiplication-cast-to-long
* @tags reliability
* security

View File

@@ -5,7 +5,7 @@
* @kind problem
* @problem.severity error
* @security-severity 7.5
* @precision medium
* @precision high
* @id cpp/wrong-type-format-argument
* @tags reliability
* correctness

View File

@@ -14,6 +14,9 @@ function may behave unpredictably.</p>
<p>This may indicate a misspelled function name, or that the required header containing
the function declaration has not been included.</p>
<p>Note: This query is not compatible with <code>build mode: none</code> databases, and produces
no results on those databases.</p>
</overview>
<recommendation>
<p>Provide an explicit declaration of the function before invoking it.</p>
@@ -26,4 +29,4 @@ the function declaration has not been included.</p>
<references>
<li>SEI CERT C Coding Standard: <a href="https://wiki.sei.cmu.edu/confluence/display/c/DCL31-C.+Declare+identifiers+before+using+them">DCL31-C. Declare identifiers before using them</a></li>
</references>
</qhelp>
</qhelp>

View File

@@ -5,7 +5,7 @@
* may lead to unpredictable behavior.
* @kind problem
* @problem.severity warning
* @precision medium
* @precision high
* @id cpp/implicit-function-declaration
* @tags correctness
* maintainability
@@ -17,6 +17,11 @@ import TooFewArguments
import TooManyArguments
import semmle.code.cpp.commons.Exclusions
/*
* This query is not compatible with build mode: none databases, and produces
* no results on those databases.
*/
predicate locInfo(Locatable e, File file, int line, int col) {
e.getFile() = file and
e.getLocation().getStartLine() = line and
@@ -39,6 +44,7 @@ predicate isCompiledAsC(File f) {
from FunctionDeclarationEntry fdeIm, FunctionCall fc
where
isCompiledAsC(fdeIm.getFile()) and
not any(Compilation c).buildModeNone() and
not isFromMacroDefinition(fc) and
fdeIm.isImplicit() and
sameLocation(fdeIm, fc) and

View File

@@ -79,9 +79,7 @@ private predicate hasZeroParamDecl(Function f) {
// True if this file (or header) was compiled as a C file
private predicate isCompiledAsC(File f) {
f.compiledAsC()
or
exists(File src | isCompiledAsC(src) | src.getAnIncludedFile() = f)
exists(File src | src.compiledAsC() | src.getAnIncludedFile*() = f)
}
predicate mistypedFunctionArguments(FunctionCall fc, Function f, Parameter p) {

View File

@@ -28,9 +28,7 @@ private predicate hasZeroParamDecl(Function f) {
/* Holds if this file (or header) was compiled as a C file. */
private predicate isCompiledAsC(File f) {
f.compiledAsC()
or
exists(File src | isCompiledAsC(src) | src.getAnIncludedFile() = f)
exists(File src | src.compiledAsC() | src.getAnIncludedFile*() = f)
}
/** Holds if `fc` is a call to `f` with too few arguments. */

View File

@@ -19,9 +19,7 @@ private predicate hasZeroParamDecl(Function f) {
// True if this file (or header) was compiled as a C file
private predicate isCompiledAsC(File f) {
f.compiledAsC()
or
exists(File src | isCompiledAsC(src) | src.getAnIncludedFile() = f)
exists(File src | src.compiledAsC() | src.getAnIncludedFile*() = f)
}
predicate tooManyArguments(FunctionCall fc, Function f) {

View File

@@ -6,7 +6,7 @@
* @kind problem
* @problem.severity warning
* @security-severity 7.8
* @precision medium
* @precision high
* @tags reliability
* security
* external/cwe/cwe-190

View File

@@ -6,7 +6,7 @@
* @kind problem
* @problem.severity warning
* @security-severity 8.8
* @precision medium
* @precision high
* @id cpp/suspicious-add-sizeof
* @tags security
* external/cwe/cwe-468

View File

@@ -0,0 +1,10 @@
## 1.6.1
### Minor Analysis Improvements
* Added `AllocationFunction` models for `aligned_alloc`, `std::aligned_alloc`, and `bsl::aligned_alloc`.
* The "Comparison of narrow type with wide type in loop condition" (`cpp/comparison-with-wider-type`) query has been upgraded to `high` precision. This query will now run in the default code scanning suite.
* The "Multiplication result converted to larger type" (`cpp/integer-multiplication-cast-to-long`) query has been upgraded to `high` precision. This query will now run in the default code scanning suite.
* The "Suspicious add with sizeof" (`cpp/suspicious-add-sizeof`) query has been upgraded to `high` precision. This query will now run in the default code scanning suite.
* The "Wrong type of arguments to formatting function" (`cpp/wrong-type-format-argument`) query has been upgraded to `high` precision. This query will now run in the default code scanning suite.
* The "Implicit function declaration" (`cpp/implicit-function-declaration`) query has been upgraded to `high` precision. However, for `build mode: none` databases, it no longer produces any results. The results in this mode were found to be very noisy and fundamentally imprecise.

View File

@@ -1,2 +1,2 @@
---
lastReleaseVersion: 1.6.0
lastReleaseVersion: 1.6.1

View File

@@ -1,5 +1,5 @@
name: codeql/cpp-queries
version: 1.6.0
version: 1.6.2-dev
groups:
- cpp
- queries

View File

@@ -0,0 +1,2 @@
| conftest.c.c:4:3:4:8 | call to strlen | This expression has no effect (because $@ has no external side effects). | conftest.h:3:8:3:13 | strlen | strlen |
| conftest_abc.c:4:3:4:8 | call to strlen | This expression has no effect (because $@ has no external side effects). | conftest.h:3:8:3:13 | strlen | strlen |

View File

@@ -0,0 +1 @@
Likely Bugs/Likely Typos/ExprHasNoEffect.ql

View File

@@ -0,0 +1,6 @@
#include "conftest.h"
int main2() {
strlen(""); // GOOD: conftest files are ignored
return 0;
}

View File

@@ -0,0 +1,6 @@
#include "conftest.h"
int main3() {
strlen(""); // BAD: not a `conftest` file, as `conftest` is not directly followed by the extension or a sequence of numbers.
return 0;
}

View File

@@ -0,0 +1,6 @@
#include "conftest.h"
int main4() {
strlen(""); // GOOD: conftest files are ignored
return 0;
}

View File

@@ -0,0 +1,3 @@
typedef long long size_t;
size_t strlen(const char *s);

View File

@@ -0,0 +1,6 @@
#include "conftest.h"
int main5() {
strlen(""); // GOOD: conftest files are ignored
return 0;
}

View File

@@ -0,0 +1,6 @@
#include "conftest.h"
int main1() {
strlen(""); // BAD: not a `conftest` file, as `conftest` is not directly followed by the extension or a sequence of numbers.
return 0;
}

View File

@@ -1,3 +1,7 @@
## 1.7.65
No user-facing changes.
## 1.7.64
No user-facing changes.

View File

@@ -0,0 +1,3 @@
## 1.7.65
No user-facing changes.

View File

@@ -1,2 +1,2 @@
---
lastReleaseVersion: 1.7.64
lastReleaseVersion: 1.7.65

View File

@@ -1,5 +1,5 @@
name: codeql/csharp-solorigate-all
version: 1.7.64
version: 1.7.66-dev
groups:
- csharp
- solorigate

View File

@@ -1,3 +1,7 @@
## 1.7.65
No user-facing changes.
## 1.7.64
No user-facing changes.

View File

@@ -13,11 +13,13 @@ import csharp
import Solorigate
import experimental.code.csharp.Cryptography.NonCryptographicHashes
ControlFlowNode loopExitNode(LoopStmt loop) { result.isAfter(loop) }
from Variable v, Literal l, LoopStmt loop, Expr additional_xor
where
maybeUsedInFnvFunction(v, _, _, loop) and
exists(BitwiseXorOperation xor2 | xor2.getAnOperand() = l and additional_xor = xor2 |
loop.getAControlFlowExitNode().getASuccessor*() = xor2.getAControlFlowNode() and
loopExitNode(loop).getASuccessor*() = xor2.getControlFlowNode() and
xor2.getAnOperand() = v.getAnAccess()
)
select l, "This literal is used in an $@ after an FNV-like hash calculation with variable $@.",

View File

@@ -0,0 +1,3 @@
## 1.7.65
No user-facing changes.

View File

@@ -1,2 +1,2 @@
---
lastReleaseVersion: 1.7.64
lastReleaseVersion: 1.7.65

View File

@@ -1,5 +1,5 @@
name: codeql/csharp-solorigate-queries
version: 1.7.64
version: 1.7.66-dev
groups:
- csharp
- solorigate

View File

@@ -1,5 +1,2 @@
import csharp
import semmle.code.csharp.controlflow.internal.Completion
import ControlFlow
import semmle.code.csharp.controlflow.internal.ControlFlowGraphImpl::Consistency
import semmle.code.csharp.controlflow.internal.Splitting
import ControlFlow::Consistency

View File

@@ -1,5 +1,4 @@
import csharp
private import semmle.code.csharp.controlflow.internal.ControlFlowGraphImpl as ControlFlowGraphImpl
private import semmle.code.csharp.dataflow.internal.DataFlowImplSpecific
private import semmle.code.csharp.dataflow.internal.TaintTrackingImplSpecific
private import codeql.dataflow.internal.DataFlowImplConsistency
@@ -7,20 +6,6 @@ private import codeql.dataflow.internal.DataFlowImplConsistency
private module Input implements InputSig<Location, CsharpDataFlow> {
private import CsharpDataFlow
private predicate isStaticAssignable(Assignable a) { a.(Modifiable).isStatic() }
predicate uniqueEnclosingCallableExclude(Node node) {
// TODO: Remove once static initializers are folded into the
// static constructors
isStaticAssignable(ControlFlowGraphImpl::getNodeCfgScope(node.getControlFlowNode()))
}
predicate uniqueCallEnclosingCallableExclude(DataFlowCall call) {
// TODO: Remove once static initializers are folded into the
// static constructors
isStaticAssignable(ControlFlowGraphImpl::getNodeCfgScope(call.getControlFlowNode()))
}
predicate uniqueNodeLocationExclude(Node n) {
// Methods with multiple implementations
n instanceof ParameterNode
@@ -70,17 +55,14 @@ private module Input implements InputSig<Location, CsharpDataFlow> {
init.getInitializer().getNumberOfChildren() > 1
)
or
exists(ControlFlow::Nodes::ElementNode cfn, ControlFlow::Nodes::Split split |
exists(arg.asExprAtNode(cfn))
|
split = cfn.getASplit() and
not split = call.getControlFlowNode().getASplit()
or
split = call.getControlFlowNode().getASplit() and
not split = cfn.getASplit()
)
or
call.(NonDelegateDataFlowCall).getDispatchCall().isReflection()
or
// Exclude calls that are both getter and setter calls, as they share the same argument nodes.
exists(AccessorCall ac |
call.(NonDelegateDataFlowCall).getDispatchCall().getCall() = ac and
ac instanceof AssignableRead and
ac instanceof AssignableWrite
)
)
}
}

View File

@@ -1,13 +1,6 @@
import csharp
import semmle.code.csharp.dataflow.internal.DataFlowPrivate::VariableCapture::Flow::ConsistencyChecks
private import semmle.code.csharp.dataflow.internal.DataFlowPrivate::VariableCapture::Flow::ConsistencyChecks as ConsistencyChecks
private import semmle.code.csharp.controlflow.BasicBlocks
private import semmle.code.csharp.controlflow.internal.ControlFlowGraphImpl
query predicate uniqueEnclosingCallable(BasicBlock bb, string msg) {
ConsistencyChecks::uniqueEnclosingCallable(bb, msg) and
getNodeCfgScope(bb.getFirstNode()) instanceof Callable
}
query predicate consistencyOverview(string msg, int n) { none() }

View File

@@ -9,5 +9,5 @@
import csharp
from IntegerLiteral literal
where literal.getValue().toInt() = 0
where literal.getIntValue() = 0
select literal

View File

@@ -1,3 +1,9 @@
## 5.5.0
### Deprecated APIs
* The predicates `get[L|R]Value` in the class `Assignment` have been deprecated. Use `get[Left|Right]Operand` instead.
## 5.4.12
### Minor Analysis Improvements

View File

@@ -77,7 +77,7 @@ predicate missedAllOpportunity(ForeachStmtGenericEnumerable fes) {
// The then case of the if assigns false to something and breaks out of the loop.
exists(Assignment a, BoolLiteral bl |
a = is.getThen().getAChild*() and
bl = a.getRValue() and
bl = a.getRightOperand() and
bl.toString() = "false"
) and
is.getThen().getAChild*() instanceof BreakStmt
@@ -121,15 +121,17 @@ predicate missedOfTypeOpportunity(ForeachStmtEnumerable fes, LocalVariableDeclSt
/**
* Holds if `foreach` statement `fes` can be converted to a `.Select()` call.
* That is, the loop variable is accessed only in the first statement of the
* block, the access is not a cast, and the first statement is a
* local variable declaration statement `s`.
* block, the access is not a cast, the first statement is a
* local variable declaration statement `s`, and the initializer does not
* contain an `await` expression (since `Select` does not support async lambdas).
*/
predicate missedSelectOpportunity(ForeachStmtGenericEnumerable fes, LocalVariableDeclStmt s) {
s = firstStmt(fes) and
forex(VariableAccess va | va = fes.getVariable().getAnAccess() |
va = s.getAVariableDeclExpr().getAChildExpr*()
) and
not s.getAVariableDeclExpr().getInitializer() instanceof Cast
not s.getAVariableDeclExpr().getInitializer() instanceof Cast and
not s.getAVariableDeclExpr().getInitializer().getAChildExpr*() instanceof AwaitExpr
}
/**

View File

@@ -0,0 +1,4 @@
---
category: feature
---
* Data flow barriers and barrier guards can now be added using data extensions. For more information see [Customizing library models for C#](https://codeql.github.com/docs/codeql-language-guides/customizing-library-models-for-csharp/).

View File

@@ -0,0 +1,4 @@
---
category: minorAnalysis
---
* Expanded ASP and ASP.NET remote source modeling to cover additional sources, including fields of tainted parameters as well as properties and fields that become tainted transitively.

View File

@@ -0,0 +1,20 @@
---
category: breaking
---
* The C# control flow graph (CFG) implementation has been completely
rewritten. The CFG now includes additional nodes to more accurately represent
certain constructs. This also means that any existing code that implicitly
relies on very specific details about the CFG may need to be updated.
The CFG no longer uses splitting, which means that AST nodes now have a unique
CFG node representation.
Additionally, the following breaking changes have been made:
- `ControlFlow::Node` has been renamed to `ControlFlowNode`.
- `ControlFlow::Nodes` has been renamed to `ControlFlowNodes`.
- `BasicBlock.getCallable` has been renamed to `BasicBlock.getEnclosingCallable`.
- `BasicBlocks.qll` has been deleted.
- `ControlFlowNode.getAstNode` has changed its meaning. The AST-to-CFG
mapping remains one-to-many, but now for a different reason. It used to be
because of splitting, but now it's because of additional "helper" CFG
nodes. To get the (now canonical) CFG node for a given AST node, use
`ControlFlowNode.asExpr()` or `ControlFlowNode.asStmt()` or
`ControlFlowElement.getControlFlowNode()` instead.

View File

@@ -0,0 +1,5 @@
## 5.5.0
### Deprecated APIs
* The predicates `get[L|R]Value` in the class `Assignment` have been deprecated. Use `get[Left|Right]Operand` instead.

View File

@@ -1,2 +1,2 @@
---
lastReleaseVersion: 5.4.12
lastReleaseVersion: 5.5.0

View File

@@ -96,7 +96,7 @@ private class MethodUse extends Use, QualifiableExpr {
private class AccessUse extends Access, Use {
AccessUse() {
not this.getTarget().(Parameter).getCallable() instanceof Accessor and
not this = any(LocalVariableDeclAndInitExpr d).getLValue() and
not this = any(LocalVariableDeclAndInitExpr d).getLeftOperand() and
not this.isImplicit() and
not this instanceof MethodAccess and // handled by `MethodUse`
not this instanceof TypeAccess and // handled by `TypeMentionUse`

View File

@@ -30,7 +30,7 @@ predicate maybeUsedInFnvFunction(Variable v, Operation xor, Operation mul, LoopS
e2.getAChild*() = v.getAnAccess() and
e1 = xor.getAnOperand() and
e2 = mul.getAnOperand() and
xor.getAControlFlowNode().getASuccessor*() = mul.getAControlFlowNode() and
xor.getControlFlowNode().getASuccessor*() = mul.getControlFlowNode() and
(xor instanceof AssignXorExpr or xor instanceof BitwiseXorExpr) and
(mul instanceof AssignMulExpr or mul instanceof MulExpr)
) and
@@ -55,11 +55,11 @@ private predicate maybeUsedInElfHashFunction(Variable v, Operation xor, Operatio
v = addAssign.getTargetVariable() and
addAssign.getAChild*() = add and
(xor instanceof BitwiseXorExpr or xor instanceof AssignXorExpr) and
addAssign.getAControlFlowNode().getASuccessor*() = xor.getAControlFlowNode() and
addAssign.getControlFlowNode().getASuccessor*() = xor.getControlFlowNode() and
xorAssign.getAChild*() = xor and
v = xorAssign.getTargetVariable() and
(notOp instanceof UnaryBitwiseOperation or notOp instanceof AssignBitwiseOperation) and
xor.getAControlFlowNode().getASuccessor*() = notOp.getAControlFlowNode() and
xor.getControlFlowNode().getASuccessor*() = notOp.getControlFlowNode() and
notAssign.getAChild*() = notOp and
v = notAssign.getTargetVariable() and
loop.getAChild*() = add.getEnclosingStmt() and

View File

@@ -7,7 +7,7 @@
* @tags ide-contextual-queries/print-cfg
*/
private import semmle.code.csharp.controlflow.internal.ControlFlowGraphImpl
import csharp
external string selectedSourceFile();
@@ -21,7 +21,7 @@ external int selectedSourceColumn();
private predicate selectedSourceColumnAlias = selectedSourceColumn/0;
module ViewCfgQueryInput implements ViewCfgQueryInputSig<File> {
module ViewCfgQueryInput implements ControlFlow::ViewCfgQueryInputSig<File> {
predicate selectedSourceFile = selectedSourceFileAlias/0;
predicate selectedSourceLine = selectedSourceLineAlias/0;
@@ -29,7 +29,7 @@ module ViewCfgQueryInput implements ViewCfgQueryInputSig<File> {
predicate selectedSourceColumn = selectedSourceColumnAlias/0;
predicate cfgScopeSpan(
CfgScope scope, File file, int startLine, int startColumn, int endLine, int endColumn
Callable scope, File file, int startLine, int startColumn, int endLine, int endColumn
) {
file = scope.getFile() and
scope.getLocation().getStartLine() = startLine and
@@ -40,11 +40,20 @@ module ViewCfgQueryInput implements ViewCfgQueryInputSig<File> {
|
loc = scope.(Callable).getBody().getLocation()
or
loc = scope.(Field).getInitializer().getLocation()
loc = any(AssignExpr init | scope.(ObjectInitMethod).initializes(init)).getLocation()
or
loc = scope.(Property).getInitializer().getLocation()
exists(AssignableMember a, Constructor ctor |
scope = ctor and
ctor.isStatic() and
a.isStatic() and
a.getDeclaringType() = ctor.getDeclaringType()
|
loc = a.(Field).getInitializer().getLocation()
or
loc = a.(Property).getInitializer().getLocation()
)
)
}
}
import ViewCfgQuery<File, ViewCfgQueryInput>
import ControlFlow::ViewCfgQuery<File, ViewCfgQueryInput>

View File

@@ -1,5 +1,5 @@
name: codeql/csharp-all
version: 5.4.12
version: 5.5.1-dev
groups: csharp
dbscheme: semmlecode.csharp.dbscheme
extractor: csharp

View File

@@ -85,8 +85,8 @@ class AssignableRead extends AssignableAccess {
}
pragma[noinline]
private ControlFlow::Node getAnAdjacentReadSameVar() {
SsaImpl::adjacentReadPairSameVar(_, this.getAControlFlowNode(), result)
private ControlFlowNode getAnAdjacentReadSameVar() {
SsaImpl::adjacentReadPairSameVar(_, this.getControlFlowNode(), result)
}
/**
@@ -114,11 +114,7 @@ class AssignableRead extends AssignableAccess {
* - The read of `this.Field` on line 11 is next to the read on line 10.
*/
pragma[nomagic]
AssignableRead getANextRead() {
forex(ControlFlow::Node cfn | cfn = result.getAControlFlowNode() |
cfn = this.getAnAdjacentReadSameVar()
)
}
AssignableRead getANextRead() { result.getControlFlowNode() = this.getAnAdjacentReadSameVar() }
}
/**
@@ -235,7 +231,7 @@ private class RefArg extends AssignableAccess {
module AssignableInternal {
private predicate tupleAssignmentDefinition(AssignExpr ae, Expr leaf) {
exists(TupleExpr te |
ae.getLValue() = te and
ae.getLeftOperand() = te and
te.getAnArgument+() = leaf and
// `leaf` is either an assignable access or a local variable declaration
not leaf instanceof TupleExpr
@@ -249,8 +245,8 @@ module AssignableInternal {
*/
private predicate tupleAssignmentPair(AssignExpr ae, Expr left, Expr right) {
tupleAssignmentDefinition(ae, _) and
left = ae.getLValue() and
right = ae.getRValue()
left = ae.getLeftOperand() and
right = ae.getRightOperand()
or
exists(TupleExpr l, TupleExpr r, int i | tupleAssignmentPair(ae, l, r) |
left = l.getArgument(i) and
@@ -291,7 +287,7 @@ module AssignableInternal {
cached
newtype TAssignableDefinition =
TAssignmentDefinition(Assignment a) {
not a.getLValue() instanceof TupleExpr and
not a.getLeftOperand() instanceof TupleExpr and
not a instanceof AssignCallOperation and
not a instanceof AssignCoalesceExpr
} or
@@ -358,7 +354,7 @@ module AssignableInternal {
// Not defined by dispatch in order to avoid too conservative negative recursion error
cached
AssignableAccess getTargetAccess(AssignableDefinition def) {
def = TAssignmentDefinition(any(Assignment a | a.getLValue() = result))
def = TAssignmentDefinition(any(Assignment a | a.getLeftOperand() = result))
or
def = TTupleAssignmentDefinition(_, result)
or
@@ -381,8 +377,8 @@ module AssignableInternal {
tupleAssignmentPair(ae, ac, result)
)
or
exists(Assignment ass | ac = ass.getLValue() |
result = ass.getRValue() and
exists(Assignment ass | ac = ass.getLeftOperand() |
result = ass.getRightOperand() and
not ass instanceof AssignOperation
)
or
@@ -410,7 +406,7 @@ private import AssignableInternal
*/
class AssignableDefinition extends TAssignableDefinition {
/**
* DEPRECATED: Use `this.getExpr().getAControlFlowNode()` instead.
* DEPRECATED: Use `this.getExpr().getControlFlowNode()` instead.
*
* Gets a control flow node that updates the targeted assignable when
* reached.
@@ -419,9 +415,7 @@ class AssignableDefinition extends TAssignableDefinition {
* the definitions of `x` and `y` in `M(out x, out y)` and `(x, y) = (0, 1)`
* relate to the same call to `M` and assignment node, respectively.
*/
deprecated ControlFlow::Node getAControlFlowNode() {
result = this.getExpr().getAControlFlowNode()
}
deprecated ControlFlowNode getAControlFlowNode() { result = this.getExpr().getControlFlowNode() }
/**
* Gets the underlying expression that updates the targeted assignable when
@@ -494,7 +488,7 @@ class AssignableDefinition extends TAssignableDefinition {
*/
pragma[nomagic]
AssignableRead getAFirstRead() {
forex(ControlFlow::Node cfn | cfn = result.getAControlFlowNode() |
exists(ControlFlowNode cfn | cfn = result.getControlFlowNode() |
exists(Ssa::ExplicitDefinition def | result = def.getAFirstReadAtNode(cfn) |
this = def.getADefinition()
)
@@ -527,7 +521,7 @@ module AssignableDefinitions {
Assignment getAssignment() { result = a }
override Expr getSource() {
result = a.getRValue() and
result = a.getRightOperand() and
not a instanceof AddOrRemoveEventExpr
}
@@ -572,11 +566,9 @@ module AssignableDefinitions {
}
/** Holds if a node in basic block `bb` assigns to `ref` parameter `p` via definition `def`. */
private predicate basicBlockRefParamDef(
ControlFlow::BasicBlock bb, Parameter p, AssignableDefinition def
) {
private predicate basicBlockRefParamDef(BasicBlock bb, Parameter p, AssignableDefinition def) {
def = any(RefArg arg).getAnAnalyzableRefDef(p) and
bb.getANode() = def.getExpr().getAControlFlowNode()
bb.getANode() = def.getExpr().getControlFlowNode()
}
/**
@@ -585,7 +577,7 @@ module AssignableDefinitions {
* any assignments to `p`.
*/
pragma[nomagic]
private predicate parameterReachesWithoutDef(Parameter p, ControlFlow::BasicBlock bb) {
private predicate parameterReachesWithoutDef(Parameter p, BasicBlock bb) {
forall(AssignableDefinition def | basicBlockRefParamDef(bb, p, def) |
isUncertainRefCall(def.getTargetAccess())
) and
@@ -593,9 +585,7 @@ module AssignableDefinitions {
any(RefArg arg).isAnalyzable(p) and
p.getCallable().getEntryPoint() = bb.getFirstNode()
or
exists(ControlFlow::BasicBlock mid | parameterReachesWithoutDef(p, mid) |
bb = mid.getASuccessor()
)
exists(BasicBlock mid | parameterReachesWithoutDef(p, mid) | bb = mid.getASuccessor())
)
}
@@ -607,7 +597,7 @@ module AssignableDefinitions {
cached
predicate isUncertainRefCall(RefArg arg) {
arg.isPotentialAssignment() and
exists(ControlFlow::BasicBlock bb, Parameter p | arg.isAnalyzable(p) |
exists(BasicBlock bb, Parameter p | arg.isAnalyzable(p) |
parameterReachesWithoutDef(p, bb) and
bb.getLastNode() = p.getCallable().getExitPoint()
)
@@ -688,7 +678,7 @@ module AssignableDefinitions {
/** Gets the underlying parameter. */
Parameter getParameter() { result = p }
deprecated override ControlFlow::Node getAControlFlowNode() {
deprecated override ControlFlowNode getAControlFlowNode() {
result = p.getCallable().getEntryPoint()
}

View File

@@ -7,23 +7,6 @@ private import csharp
* in the same stage across different files.
*/
module Stages {
cached
module ControlFlowStage {
private import semmle.code.csharp.controlflow.internal.Splitting
cached
predicate forceCachingInSameStage() { any() }
cached
private predicate forceCachingInSameStageRev() {
exists(Split s)
or
exists(ControlFlow::Node n)
or
forceCachingInSameStageRev()
}
}
cached
module GuardsStage {
private import semmle.code.csharp.controlflow.Guards

View File

@@ -22,7 +22,7 @@ private import TypeRef
* an anonymous function (`AnonymousFunctionExpr`), or a local function
* (`LocalFunction`).
*/
class Callable extends Parameterizable, ExprOrStmtParent, @callable {
class Callable extends Parameterizable, ControlFlowElementOrCallable, @callable {
/** Gets the return type of this callable. */
Type getReturnType() { none() }
@@ -157,10 +157,10 @@ class Callable extends Parameterizable, ExprOrStmtParent, @callable {
final predicate hasExpressionBody() { exists(this.getExpressionBody()) }
/** Gets the entry point in the control graph for this callable. */
ControlFlow::Nodes::EntryNode getEntryPoint() { result.getCallable() = this }
ControlFlow::EntryNode getEntryPoint() { result.getEnclosingCallable() = this }
/** Gets the exit point in the control graph for this callable. */
ControlFlow::Nodes::ExitNode getExitPoint() { result.getCallable() = this }
ControlFlow::ExitNode getExitPoint() { result.getEnclosingCallable() = this }
/**
* Gets the enclosing callable of this callable, if any.

View File

@@ -232,14 +232,9 @@ private module Identity {
*/
pragma[nomagic]
private predicate convTypeArguments(Type fromTypeArgument, Type toTypeArgument, int i) {
exists(int j |
fromTypeArgument = getTypeArgumentRanked(_, _, i) and
toTypeArgument = getTypeArgumentRanked(_, _, j) and
i <= j and
j <= i
|
convIdentity(fromTypeArgument, toTypeArgument)
)
fromTypeArgument = getTypeArgumentRanked(_, _, pragma[only_bind_into](i)) and
toTypeArgument = getTypeArgumentRanked(_, _, pragma[only_bind_into](i)) and
convIdentity(fromTypeArgument, toTypeArgument)
}
pragma[nomagic]
@@ -718,7 +713,7 @@ private class SignedIntegralConstantExpr extends Expr {
}
private predicate convConstantIntExpr(SignedIntegralConstantExpr e, SimpleType toType) {
exists(int n | n = e.getValue().toInt() |
exists(int n | n = e.getIntValue() |
toType = any(SByteType t | n in [t.minValue() .. t.maxValue()])
or
toType = any(ByteType t | n in [t.minValue() .. t.maxValue()])
@@ -735,7 +730,7 @@ private predicate convConstantIntExpr(SignedIntegralConstantExpr e, SimpleType t
private predicate convConstantLongExpr(SignedIntegralConstantExpr e) {
e.getType() instanceof LongType and
e.getValue().toInt() >= 0
e.getIntValue() >= 0
}
/** 6.1.10: Implicit reference conversions involving type parameters. */
@@ -929,19 +924,16 @@ private module Variance {
private predicate convTypeArguments(
TypeArgument fromTypeArgument, TypeArgument toTypeArgument, int i, TVariance v
) {
exists(int j |
fromTypeArgument = getTypeArgumentRanked(_, _, i, _) and
toTypeArgument = getTypeArgumentRanked(_, _, j, _) and
i <= j and
j <= i
|
fromTypeArgument = getTypeArgumentRanked(_, _, pragma[only_bind_into](i), _) and
toTypeArgument = getTypeArgumentRanked(_, _, pragma[only_bind_into](i), _) and
(
convIdentity(fromTypeArgument, toTypeArgument) and
v = TNone()
or
convRefTypeTypeArgumentOut(fromTypeArgument, toTypeArgument, j) and
convRefTypeTypeArgumentOut(fromTypeArgument, toTypeArgument, i) and
v = TOut()
or
convRefTypeTypeArgumentIn(toTypeArgument, fromTypeArgument, j) and
convRefTypeTypeArgumentIn(toTypeArgument, fromTypeArgument, i) and
v = TIn()
)
}

View File

@@ -129,13 +129,6 @@ private module Cached {
result = parent.getAChildStmt()
}
pragma[inline]
private ControlFlowElement enclosingStart(ControlFlowElement cfe) {
result = cfe
or
getAChild(result).(AnonymousFunctionExpr) = cfe
}
private predicate parent(ControlFlowElement child, ExprOrStmtParent parent) {
child = getAChild(parent) and
not child = getBody(_)
@@ -145,7 +138,7 @@ private module Cached {
cached
predicate enclosingBody(ControlFlowElement cfe, ControlFlowElement body) {
body = getBody(_) and
parent*(enclosingStart(cfe), body)
parent*(cfe, body)
}
/** Holds if the enclosing callable of `cfe` is `c`. */
@@ -153,7 +146,7 @@ private module Cached {
predicate enclosingCallable(ControlFlowElement cfe, Callable c) {
enclosingBody(cfe, getBody(c))
or
parent*(enclosingStart(cfe), c.(Constructor).getInitializer())
parent*(cfe, c.(Constructor).getInitializer())
or
parent*(cfe, c.(Constructor).getObjectInitializerCall())
or

View File

@@ -343,10 +343,10 @@ final class AssignmentNode extends ControlFlowElementNode {
result.(TypeMentionNode).getTarget() = controlFlowElement
or
childIndex = 0 and
result.(ElementNode).getElement() = assignment.getLValue()
result.(ElementNode).getElement() = assignment.getLeftOperand()
or
childIndex = 1 and
result.(ElementNode).getElement() = assignment.getRValue()
result.(ElementNode).getElement() = assignment.getRightOperand()
}
}

View File

@@ -535,8 +535,8 @@ class Setter extends Accessor, @setter {
exists(AssignExpr assign |
this.getStatementBody().getNumberOfStmts() = 1 and
assign.getParent() = this.getStatementBody().getAChild() and
assign.getLValue() = result.getAnAccess() and
assign.getRValue() = accessToValue()
assign.getLeftOperand() = result.getAnAccess() and
assign.getRightOperand() = accessToValue()
)
}

View File

@@ -54,21 +54,44 @@ private string genericCollectionTypeName() {
]
}
/** A collection type. */
class CollectionType extends RefType {
CollectionType() {
exists(RefType base | base = this.getABaseType*() |
base.hasFullyQualifiedName(collectionNamespaceName(), collectionTypeName())
or
base.(ConstructedType)
.getUnboundGeneric()
.hasFullyQualifiedName(genericCollectionNamespaceName(), genericCollectionTypeName())
)
or
this instanceof ArrayType
/** A collection type */
abstract private class CollectionTypeImpl extends RefType {
/**
* Gets the element type of this collection, for example `int` in `List<int>`.
*/
abstract Type getElementType();
}
private class GenericCollectionType extends CollectionTypeImpl {
private ConstructedType base;
GenericCollectionType() {
base = this.getABaseType*() and
base.getUnboundGeneric()
.hasFullyQualifiedName(genericCollectionNamespaceName(), genericCollectionTypeName())
}
override Type getElementType() {
result = base.getTypeArgument(0) and base.getNumberOfTypeArguments() = 1
}
}
private class NonGenericCollectionType extends CollectionTypeImpl {
NonGenericCollectionType() {
exists(RefType base | base = this.getABaseType*() |
base.hasFullyQualifiedName(collectionNamespaceName(), collectionTypeName())
)
}
override Type getElementType() { none() }
}
private class ArrayCollectionType extends CollectionTypeImpl instanceof ArrayType {
override Type getElementType() { result = ArrayType.super.getElementType() }
}
final class CollectionType = CollectionTypeImpl;
/**
* A collection type that can be used as a `params` parameter type.
*/

View File

@@ -161,7 +161,7 @@ private newtype TComparisonTest =
compare.getComparisonKind().isCompare() and
outerKind = outer.getComparisonKind() and
outer.getAnArgument() = compare.getExpr() and
i = outer.getAnArgument().getValue().toInt()
i = outer.getAnArgument().getIntValue()
|
outerKind.isEquality() and
(

View File

@@ -32,13 +32,13 @@ private module ConstantComparisonOperation {
private int maxValue(Expr expr) {
if convertedType(expr) instanceof IntegralType and exists(expr.getValue())
then result = expr.getValue().toInt()
then result = expr.getIntValue()
else result = convertedType(expr).maxValue()
}
private int minValue(Expr expr) {
if convertedType(expr) instanceof IntegralType and exists(expr.getValue())
then result = expr.getValue().toInt()
then result = expr.getIntValue()
else result = convertedType(expr).minValue()
}

View File

@@ -29,7 +29,7 @@ class ImplicitToStringExpr extends Expr {
m = p.getCallable()
|
m = any(SystemTextStringBuilderClass c).getAMethod() and
m.getName().regexpMatch("Append(Line)?") and
m.getName() = "Append" and
not p.getType() instanceof ArrayType
or
p instanceof StringFormatItemParameter and

View File

@@ -1,356 +0,0 @@
/**
* Provides classes representing basic blocks.
*/
import csharp
private import ControlFlow
private import semmle.code.csharp.controlflow.internal.ControlFlowGraphImpl as CfgImpl
private import CfgImpl::BasicBlocks as BasicBlocksImpl
private import codeql.controlflow.BasicBlock as BB
/**
* A basic block, that is, a maximal straight-line sequence of control flow nodes
* without branches or joins.
*/
final class BasicBlock extends BasicBlocksImpl::BasicBlock {
/** Gets an immediate successor of this basic block of a given type, if any. */
BasicBlock getASuccessor(ControlFlow::SuccessorType t) { result = super.getASuccessor(t) }
/** DEPRECATED: Use `getASuccessor` instead. */
deprecated BasicBlock getASuccessorByType(ControlFlow::SuccessorType t) {
result = this.getASuccessor(t)
}
/** Gets an immediate predecessor of this basic block of a given type, if any. */
BasicBlock getAPredecessorByType(ControlFlow::SuccessorType t) {
result = this.getAPredecessor(t)
}
/**
* Gets an immediate `true` successor, if any.
*
* An immediate `true` successor is a successor that is reached when
* the condition that ends this basic block evaluates to `true`.
*
* Example:
*
* ```csharp
* if (x < 0)
* x = -x;
* ```
*
* The basic block on line 2 is an immediate `true` successor of the
* basic block on line 1.
*/
BasicBlock getATrueSuccessor() { result.getFirstNode() = this.getLastNode().getATrueSuccessor() }
/**
* Gets an immediate `false` successor, if any.
*
* An immediate `false` successor is a successor that is reached when
* the condition that ends this basic block evaluates to `false`.
*
* Example:
*
* ```csharp
* if (!(x >= 0))
* x = -x;
* ```
*
* The basic block on line 2 is an immediate `false` successor of the
* basic block on line 1.
*/
BasicBlock getAFalseSuccessor() {
result.getFirstNode() = this.getLastNode().getAFalseSuccessor()
}
BasicBlock getASuccessor() { result = super.getASuccessor() }
/** Gets the control flow node at a specific (zero-indexed) position in this basic block. */
ControlFlow::Node getNode(int pos) { result = super.getNode(pos) }
/** Gets a control flow node in this basic block. */
ControlFlow::Node getANode() { result = super.getANode() }
/** Gets the first control flow node in this basic block. */
ControlFlow::Node getFirstNode() { result = super.getFirstNode() }
/** Gets the last control flow node in this basic block. */
ControlFlow::Node getLastNode() { result = super.getLastNode() }
/** Gets the callable that this basic block belongs to. */
final Callable getCallable() { result = this.getFirstNode().getEnclosingCallable() }
/**
* Holds if this basic block immediately dominates basic block `bb`.
*
* That is, this basic block is the unique basic block satisfying:
* 1. This basic block strictly dominates `bb`
* 2. There exists no other basic block that is strictly dominated by this
* basic block and which strictly dominates `bb`.
*
* All basic blocks, except entry basic blocks, have a unique immediate
* dominator.
*
* Example:
*
* ```csharp
* int M(string s) {
* if (s == null)
* throw new ArgumentNullException(nameof(s));
* return s.Length;
* }
* ```
*
* The basic block starting on line 2 strictly dominates the
* basic block on line 4 (all paths from the entry point of `M`
* to `return s.Length;` must go through the null check).
*/
predicate immediatelyDominates(BasicBlock bb) { super.immediatelyDominates(bb) }
/**
* Holds if this basic block strictly dominates basic block `bb`.
*
* That is, all paths reaching basic block `bb` from some entry point
* basic block must go through this basic block (which must be different
* from `bb`).
*
* Example:
*
* ```csharp
* int M(string s) {
* if (s == null)
* throw new ArgumentNullException(nameof(s));
* return s.Length;
* }
* ```
*
* The basic block starting on line 2 strictly dominates the
* basic block on line 4 (all paths from the entry point of `M`
* to `return s.Length;` must go through the null check).
*/
predicate strictlyDominates(BasicBlock bb) { super.strictlyDominates(bb) }
/**
* Holds if this basic block dominates basic block `bb`.
*
* That is, all paths reaching basic block `bb` from some entry point
* basic block must go through this basic block.
*
* Example:
*
* ```csharp
* int M(string s) {
* if (s == null)
* throw new ArgumentNullException(nameof(s));
* return s.Length;
* }
* ```
*
* The basic block starting on line 2 dominates the basic
* block on line 4 (all paths from the entry point of `M` to
* `return s.Length;` must go through the null check).
*
* This predicate is *reflexive*, so for example `if (s == null)` dominates
* itself.
*/
predicate dominates(BasicBlock bb) {
bb = this or
this.strictlyDominates(bb)
}
/**
* Holds if `df` is in the dominance frontier of this basic block.
* That is, this basic block dominates a predecessor of `df`, but
* does not dominate `df` itself.
*
* Example:
*
* ```csharp
* if (x < 0) {
* x = -x;
* if (x > 10)
* x--;
* }
* Console.Write(x);
* ```
*
* The basic block on line 6 is in the dominance frontier
* of the basic block starting on line 2 because that block
* dominates the basic block on line 4, which is a predecessor of
* `Console.Write(x);`. Also, the basic block starting on line 2
* does not dominate the basic block on line 6.
*/
predicate inDominanceFrontier(BasicBlock df) { super.inDominanceFrontier(df) }
/**
* Gets the basic block that immediately dominates this basic block, if any.
*
* That is, the result is the unique basic block satisfying:
* 1. The result strictly dominates this basic block.
* 2. There exists no other basic block that is strictly dominated by the
* result and which strictly dominates this basic block.
*
* All basic blocks, except entry basic blocks, have a unique immediate
* dominator.
*
* Example:
*
* ```csharp
* int M(string s) {
* if (s == null)
* throw new ArgumentNullException(nameof(s));
* return s.Length;
* }
* ```
*
* The basic block starting on line 2 is an immediate dominator of
* the basic block online 4 (all paths from the entry point of `M`
* to `return s.Length;` must go through the null check.
*/
BasicBlock getImmediateDominator() { result = super.getImmediateDominator() }
/**
* Holds if the edge with successor type `s` out of this basic block is a
* dominating edge for `dominated`.
*
* That is, all paths reaching `dominated` from the entry point basic
* block must go through the `s` edge out of this basic block.
*
* Edge dominance is similar to node dominance except it concerns edges
* instead of nodes: A basic block is dominated by a _basic block_ `bb` if it
* can only be reached through `bb` and dominated by an _edge_ `e` if it can
* only be reached through `e`.
*
* Note that where all basic blocks (except the entry basic block) are
* strictly dominated by at least one basic block, a basic block may not be
* dominated by any edge. If an edge dominates a basic block `bb`, then
* both endpoints of the edge dominates `bb`. The converse is not the case,
* as there may be multiple paths between the endpoints with none of them
* dominating.
*/
predicate edgeDominates(BasicBlock dominated, ControlFlow::SuccessorType s) {
super.edgeDominates(dominated, s)
}
/**
* Holds if this basic block strictly post-dominates basic block `bb`.
*
* That is, all paths reaching a normal exit point basic block from basic
* block `bb` must go through this basic block (which must be different
* from `bb`).
*
* Example:
*
* ```csharp
* int M(string s) {
* try {
* return s.Length;
* }
* finally {
* Console.WriteLine("M");
* }
* }
* ```
*
* The basic block on line 6 strictly post-dominates the basic block on
* line 3 (all paths to the exit point of `M` from `return s.Length;`
* must go through the `WriteLine` call).
*/
predicate strictlyPostDominates(BasicBlock bb) { super.strictlyPostDominates(bb) }
/**
* Holds if this basic block post-dominates basic block `bb`.
*
* That is, all paths reaching a normal exit point basic block from basic
* block `bb` must go through this basic block.
*
* Example:
*
* ```csharp
* int M(string s) {
* try {
* return s.Length;
* }
* finally {
* Console.WriteLine("M");
* }
* }
* ```
*
* The basic block on line 6 post-dominates the basic block on line 3
* (all paths to the exit point of `M` from `return s.Length;` must go
* through the `WriteLine` call).
*
* This predicate is *reflexive*, so for example `Console.WriteLine("M");`
* post-dominates itself.
*/
predicate postDominates(BasicBlock bb) { super.postDominates(bb) }
/**
* Holds if this basic block is in a loop in the control flow graph. This
* includes loops created by `goto` statements. This predicate may not hold
* even if this basic block is syntactically inside a `while` loop if the
* necessary back edges are unreachable.
*/
predicate inLoop() { this.getASuccessor+() = this }
}
/**
* An entry basic block, that is, a basic block whose first node is
* an entry node.
*/
final class EntryBasicBlock extends BasicBlock, BasicBlocksImpl::EntryBasicBlock { }
/**
* An annotated exit basic block, that is, a basic block that contains an
* annotated exit node.
*/
final class AnnotatedExitBasicBlock extends BasicBlock, BasicBlocksImpl::AnnotatedExitBasicBlock { }
/**
* An exit basic block, that is, a basic block whose last node is
* an exit node.
*/
final class ExitBasicBlock extends BasicBlock, BasicBlocksImpl::ExitBasicBlock { }
/** A basic block with more than one predecessor. */
final class JoinBlock extends BasicBlock, BasicBlocksImpl::JoinBasicBlock {
JoinBlockPredecessor getJoinBlockPredecessor(int i) { result = super.getJoinBlockPredecessor(i) }
}
/** A basic block that is an immediate predecessor of a join block. */
final class JoinBlockPredecessor extends BasicBlock, BasicBlocksImpl::JoinPredecessorBasicBlock { }
/**
* A basic block that terminates in a condition, splitting the subsequent
* control flow.
*/
final class ConditionBlock extends BasicBlock, BasicBlocksImpl::ConditionBasicBlock {
/** DEPRECATED: Use `edgeDominates` instead. */
deprecated predicate immediatelyControls(BasicBlock succ, ConditionalSuccessor s) {
this.getASuccessor(s) = succ and
BasicBlocksImpl::dominatingEdge(this, succ)
}
/** DEPRECATED: Use `edgeDominates` instead. */
deprecated predicate controls(BasicBlock controlled, ConditionalSuccessor s) {
super.edgeDominates(controlled, s)
}
}
private class BasicBlockAlias = BasicBlock;
private class EntryBasicBlockAlias = EntryBasicBlock;
module Cfg implements BB::CfgSig<Location> {
class ControlFlowNode = ControlFlow::Node;
class BasicBlock = BasicBlockAlias;
class EntryBasicBlock = EntryBasicBlockAlias;
predicate dominatingEdge(BasicBlock bb1, BasicBlock bb2) {
BasicBlocksImpl::dominatingEdge(bb1, bb2)
}
}

View File

@@ -4,20 +4,21 @@ import csharp
private import semmle.code.csharp.ExprOrStmtParent
private import semmle.code.csharp.commons.Compilation
private import ControlFlow
private import ControlFlow::BasicBlocks
private import semmle.code.csharp.Caching
private import internal.ControlFlowGraphImpl as Impl
private class TControlFlowElementOrCallable = @callable or @control_flow_element;
/** A `ControlFlowElement` or a `Callable`. */
class ControlFlowElementOrCallable extends ExprOrStmtParent, TControlFlowElementOrCallable { }
/**
* A program element that can possess control flow. That is, either a statement or
* an expression.
*
* A control flow element can be mapped to a control flow node (`ControlFlow::Node`)
* via `getAControlFlowNode()`. There is a one-to-many relationship between
* control flow elements and control flow nodes. This allows control flow
* splitting, for example modeling the control flow through `finally` blocks.
* A control flow element can be mapped to a control flow node (`ControlFlowNode`)
* via `getControlFlowNode()`.
*/
class ControlFlowElement extends ExprOrStmtParent, @control_flow_element {
class ControlFlowElement extends ControlFlowElementOrCallable, @control_flow_element {
/** Gets the enclosing callable of this element, if any. */
Callable getEnclosingCallable() { none() }
@@ -30,41 +31,26 @@ class ControlFlowElement extends ExprOrStmtParent, @control_flow_element {
}
/**
* DEPRECATED: Use `getControlFlowNode()` instead.
*
* Gets a control flow node for this element. That is, a node in the
* control flow graph that corresponds to this element.
*
* Typically, there is exactly one `ControlFlow::Node` associated with a
* `ControlFlowElement`, but a `ControlFlowElement` may be split into
* several `ControlFlow::Node`s, for example to represent the continuation
* flow in a `try/catch/finally` construction.
*/
Nodes::ElementNode getAControlFlowNode() { result.getAstNode() = this }
deprecated ControlFlowNodes::ElementNode getAControlFlowNode() {
result = this.getControlFlowNode()
}
/** Gets the control flow node for this element. */
ControlFlow::Node getControlFlowNode() { result.getAstNode() = this }
/** Gets the control flow node for this element, if any. */
ControlFlowNode getControlFlowNode() { result.injects(this) }
/** Gets the basic block in which this element occurs. */
BasicBlock getBasicBlock() { result = this.getAControlFlowNode().getBasicBlock() }
/**
* Gets a first control flow node executed within this element.
*/
Nodes::ElementNode getAControlFlowEntryNode() {
result = Impl::getAControlFlowEntryNode(this).(ControlFlowElement).getAControlFlowNode()
}
/**
* Gets a potential last control flow node executed within this element.
*/
Nodes::ElementNode getAControlFlowExitNode() {
result = Impl::getAControlFlowExitNode(this).(ControlFlowElement).getAControlFlowNode()
}
BasicBlock getBasicBlock() { result = this.getControlFlowNode().getBasicBlock() }
/**
* Holds if this element is live, that is this element can be reached
* from the entry point of its enclosing callable.
*/
predicate isLive() { exists(this.getAControlFlowNode()) }
predicate isLive() { exists(this.getControlFlowNode()) }
/** Holds if the current element is reachable from `src`. */
// potentially very large predicate, so must be inlined
@@ -77,31 +63,13 @@ class ControlFlowElement extends ExprOrStmtParent, @control_flow_element {
ControlFlowElement getAReachableElement() {
// Reachable in same basic block
exists(BasicBlock bb, int i, int j |
bb.getNode(i) = this.getAControlFlowNode() and
bb.getNode(j) = result.getAControlFlowNode() and
bb.getNode(i) = this.getControlFlowNode() and
bb.getNode(j) = result.getControlFlowNode() and
i < j
)
or
// Reachable in different basic blocks
this.getAControlFlowNode().getBasicBlock().getASuccessor+().getANode() =
result.getAControlFlowNode()
}
/**
* DEPRECATED: Use `Guard` class instead.
*
* Holds if basic block `controlled` is controlled by this control flow element
* with conditional value `s`. That is, `controlled` can only be reached from
* the callable entry point by going via the `s` edge out of *some* basic block
* ending with this element.
*
* `cb` records all of the possible condition blocks for this control flow element
* that a path from the callable entry point to `controlled` may go through.
*/
deprecated predicate controlsBlock(
BasicBlock controlled, ConditionalSuccessor s, ConditionBlock cb
) {
cb.getLastNode() = this.getAControlFlowNode() and
cb.edgeDominates(controlled, s)
this.getControlFlowNode().getBasicBlock().getASuccessor+().getANode() =
result.getControlFlowNode()
}
}

View File

@@ -1,315 +1,577 @@
import csharp
/**
* Provides classes representing the control flow graph within callables.
*/
module ControlFlow {
private import semmle.code.csharp.controlflow.BasicBlocks as BBs
import semmle.code.csharp.controlflow.internal.SuccessorType
private import internal.ControlFlowGraphImpl as Impl
private import internal.Splitting as Splitting
/**
* A control flow node.
*
* Either a callable entry node (`EntryNode`), a callable exit node (`ExitNode`),
* or a control flow node for a control flow element, that is, an expression or a
* statement (`ElementNode`).
*
* A control flow node is a node in the control flow graph (CFG). There is a
* many-to-one relationship between `ElementNode`s and `ControlFlowElement`s.
* This allows control flow splitting, for example modeling the control flow
* through `finally` blocks.
*
* Only nodes that can be reached from the callable entry point are included in
* the CFG.
*/
class Node extends Impl::Node {
/** Gets the control flow element that this node corresponds to, if any. */
final ControlFlowElement getAstNode() { result = super.getAstNode() }
import csharp
private import codeql.controlflow.ControlFlowGraph
private import codeql.controlflow.SuccessorType
private import semmle.code.csharp.commons.Compilation
private import semmle.code.csharp.controlflow.internal.NonReturning as NonReturning
/** Gets the basic block that this control flow node belongs to. */
BasicBlock getBasicBlock() { result.getANode() = this }
private module Cfg0 = Make0<Location, Ast>;
/**
* Holds if this node dominates `that` node.
*
* That is, all paths reaching `that` node from some callable entry
* node (`EntryNode`) must go through this node.
*
* Example:
*
* ```csharp
* int M(string s)
* {
* if (s == null)
* throw new ArgumentNullException(nameof(s));
* return s.Length;
* }
* ```
*
* The node on line 3 dominates the node on line 5 (all paths from the
* entry point of `M` to `return s.Length;` must go through the null check).
*
* This predicate is *reflexive*, so for example `if (s == null)` dominates
* itself.
*/
// potentially very large predicate, so must be inlined
pragma[inline]
predicate dominates(Node that) {
this.strictlyDominates(that)
or
this = that
}
private module Cfg1 = Make1<Input>;
/**
* Holds if this node strictly dominates `that` node.
*
* That is, all paths reaching `that` node from some callable entry
* node (`EntryNode`) must go through this node (which must
* be different from `that` node).
*
* Example:
*
* ```csharp
* int M(string s)
* {
* if (s == null)
* throw new ArgumentNullException(nameof(s));
* return s.Length;
* }
* ```
*
* The node on line 3 strictly dominates the node on line 5
* (all paths from the entry point of `M` to `return s.Length;` must go
* through the null check).
*/
// potentially very large predicate, so must be inlined
pragma[inline]
predicate strictlyDominates(Node that) {
this.getBasicBlock().strictlyDominates(that.getBasicBlock())
or
exists(BasicBlock bb, int i, int j |
bb.getNode(i) = this and
bb.getNode(j) = that and
i < j
)
}
private module Cfg2 = Make2<Input>;
/**
* Holds if this node post-dominates `that` node.
*
* That is, all paths reaching a normal callable exit node (an `AnnotatedExitNode`
* with a normal exit type) from `that` node must go through this node.
*
* Example:
*
* ```csharp
* int M(string s)
* {
* try
* {
* return s.Length;
* }
* finally
* {
* Console.WriteLine("M");
* }
* }
* ```
*
* The node on line 9 post-dominates the node on line 5 (all paths to the
* exit point of `M` from `return s.Length;` must go through the `WriteLine`
* call).
*
* This predicate is *reflexive*, so for example `Console.WriteLine("M");`
* post-dominates itself.
*/
// potentially very large predicate, so must be inlined
pragma[inline]
predicate postDominates(Node that) {
this.strictlyPostDominates(that)
or
this = that
}
private import Cfg0
private import Cfg1
private import Cfg2
import Public
/**
* Holds if this node strictly post-dominates `that` node.
*
* That is, all paths reaching a normal callable exit node (an `AnnotatedExitNode`
* with a normal exit type) from `that` node must go through this node
* (which must be different from `that` node).
*
* Example:
*
* ```csharp
* int M(string s)
* {
* try
* {
* return s.Length;
* }
* finally
* {
* Console.WriteLine("M");
* }
* }
* ```
*
* The node on line 9 strictly post-dominates the node on line 5 (all
* paths to the exit point of `M` from `return s.Length;` must go through
* the `WriteLine` call).
*/
// potentially very large predicate, so must be inlined
pragma[inline]
predicate strictlyPostDominates(Node that) {
this.getBasicBlock().strictlyPostDominates(that.getBasicBlock())
or
exists(BasicBlock bb, int i, int j |
bb.getNode(i) = this and
bb.getNode(j) = that and
i > j
)
}
/** Provides an implementation of the AST signature for C#. */
private module Ast implements AstSig<Location> {
private import csharp as CS
/** Gets a successor node of a given type, if any. */
Node getASuccessorByType(SuccessorType t) { result = this.getASuccessor(t) }
class AstNode = ControlFlowElementOrCallable;
/** Gets an immediate successor, if any. */
Node getASuccessor() { result = this.getASuccessorByType(_) }
/** Gets an immediate predecessor node of a given flow type, if any. */
Node getAPredecessorByType(SuccessorType t) { result.getASuccessorByType(t) = this }
/** Gets an immediate predecessor, if any. */
Node getAPredecessor() { result = this.getAPredecessorByType(_) }
/**
* Gets an immediate `true` successor, if any.
*
* An immediate `true` successor is a successor that is reached when
* this condition evaluates to `true`.
*
* Example:
*
* ```csharp
* if (x < 0)
* x = -x;
* ```
*
* The node on line 2 is an immediate `true` successor of the node
* on line 1.
*/
Node getATrueSuccessor() {
result = this.getASuccessorByType(any(BooleanSuccessor t | t.getValue() = true))
}
/**
* Gets an immediate `false` successor, if any.
*
* An immediate `false` successor is a successor that is reached when
* this condition evaluates to `false`.
*
* Example:
*
* ```csharp
* if (!(x >= 0))
* x = -x;
* ```
*
* The node on line 2 is an immediate `false` successor of the node
* on line 1.
*/
Node getAFalseSuccessor() {
result = this.getASuccessorByType(any(BooleanSuccessor t | t.getValue() = false))
}
/** Gets the enclosing callable of this control flow node. */
final Callable getEnclosingCallable() { result = Impl::getNodeCfgScope(this) }
additional predicate skipControlFlow(AstNode e) {
e instanceof TypeAccess and
not e instanceof TypeAccessPatternExpr
or
not e.getFile().fromSource()
}
/** Provides different types of control flow nodes. */
module Nodes {
/** A node for a callable entry point. */
class EntryNode extends Node instanceof Impl::EntryNode {
/** Gets the callable that this entry applies to. */
Callable getCallable() { result = this.getScope() }
override BasicBlocks::EntryBlock getBasicBlock() { result = Node.super.getBasicBlock() }
}
/** A node for a callable exit point, annotated with the type of exit. */
class AnnotatedExitNode extends Node instanceof Impl::AnnotatedExitNode {
/** Holds if this node represent a normal exit. */
final predicate isNormal() { super.isNormal() }
/** Gets the callable that this exit applies to. */
Callable getCallable() { result = this.getScope() }
override BasicBlocks::AnnotatedExitBlock getBasicBlock() {
result = Node.super.getBasicBlock()
}
}
/** A control flow node indicating normal termination of a callable. */
class NormalExitNode extends AnnotatedExitNode instanceof Impl::NormalExitNode { }
/** A node for a callable exit point. */
class ExitNode extends Node instanceof Impl::ExitNode {
/** Gets the callable that this exit applies to. */
Callable getCallable() { result = this.getScope() }
override BasicBlocks::ExitBlock getBasicBlock() { result = Node.super.getBasicBlock() }
}
/**
* A node for a control flow element, that is, an expression or a statement.
*
* Each control flow element maps to zero or more `ElementNode`s: zero when
* the element is in unreachable (dead) code, and multiple when there are
* different splits for the element.
*/
class ElementNode extends Node instanceof Impl::AstCfgNode {
/** Gets a comma-separated list of strings for each split in this node, if any. */
final string getSplitsString() { result = super.getSplitsString() }
/** Gets a split for this control flow node, if any. */
final Split getASplit() { result = super.getASplit() }
}
/** A control-flow node for an expression. */
class ExprNode extends ElementNode {
Expr e;
ExprNode() { e = unique(Expr e_ | e_ = this.getAstNode() | e_) }
/** Gets the expression that this control-flow node belongs to. */
Expr getExpr() { result = e }
/** Gets the value of this expression node, if any. */
string getValue() { result = e.getValue() }
/** Gets the type of this expression node. */
Type getType() { result = e.getType() }
}
class Split = Splitting::Split;
private AstNode getExprChild0(Expr e, int i) {
not e instanceof NameOfExpr and
not e instanceof AnonymousFunctionExpr and
not skipControlFlow(result) and
result = e.getChild(i)
}
class BasicBlock = BBs::BasicBlock;
private AstNode getStmtChild0(Stmt s, int i) {
not s instanceof FixedStmt and
not s instanceof UsingBlockStmt and
result = s.getChild(i)
or
s =
any(FixedStmt fs |
result = fs.getVariableDeclExpr(i)
or
result = fs.getBody() and
i = max(int j | exists(fs.getVariableDeclExpr(j))) + 1
)
or
s =
any(UsingBlockStmt us |
result = us.getExpr() and
i = 0
or
result = us.getVariableDeclExpr(i)
or
result = us.getBody() and
i = max([1, count(us.getVariableDeclExpr(_))])
)
}
/** Provides different types of basic blocks. */
module BasicBlocks {
class EntryBlock = BBs::EntryBasicBlock;
AstNode getChild(AstNode n, int index) {
result = getStmtChild0(n, index)
or
result = getExprChild0(n, index)
}
class AnnotatedExitBlock = BBs::AnnotatedExitBasicBlock;
private AstNode getParent(AstNode n) { n = getChild(result, _) }
class ExitBlock = BBs::ExitBasicBlock;
Callable getEnclosingCallable(AstNode node) {
result = node.(ControlFlowElement).getEnclosingCallable() or
result.(ObjectInitMethod).initializes(getParent*(node)) or
Initializers::staticMemberInitializer(result, getParent*(node))
}
class JoinBlock = BBs::JoinBlock;
class Callable = CS::Callable;
class JoinBlockPredecessor = BBs::JoinBlockPredecessor;
AstNode callableGetBody(Callable c) {
not skipControlFlow(result) and
result = c.getBody()
}
class ConditionBlock = BBs::ConditionBlock;
class Stmt = CS::Stmt;
class Expr = CS::Expr;
class BlockStmt = CS::BlockStmt;
class ExprStmt = CS::ExprStmt;
class IfStmt = CS::IfStmt;
class LoopStmt = CS::LoopStmt;
class WhileStmt = CS::WhileStmt;
class DoStmt = CS::DoStmt;
final private class FinalForStmt = CS::ForStmt;
class ForStmt extends FinalForStmt {
Expr getInit(int index) { result = this.getInitializer(index) }
}
final private class FinalForeachStmt = CS::ForeachStmt;
class ForeachStmt extends FinalForeachStmt {
Expr getVariable() {
result = this.getVariableDeclExpr() or result = this.getVariableDeclTuple()
}
Expr getCollection() { result = this.getIterableExpr() }
}
class BreakStmt = CS::BreakStmt;
class ContinueStmt = CS::ContinueStmt;
class GotoStmt = CS::GotoStmt;
class ReturnStmt = CS::ReturnStmt;
class Throw = CS::ThrowElement;
final private class FinalTryStmt = CS::TryStmt;
class TryStmt extends FinalTryStmt {
Stmt getBody() { result = this.getBlock() }
CatchClause getCatch(int index) { result = this.getCatchClause(index) }
Stmt getFinally() { result = super.getFinally() }
}
final private class FinalCatchClause = CS::CatchClause;
class CatchClause extends FinalCatchClause {
AstNode getVariable() { result = this.(CS::SpecificCatchClause).getVariableDeclExpr() }
Expr getCondition() { result = this.getFilterClause() }
Stmt getBody() { result = this.getBlock() }
}
final private class FinalSwitch = CS::Switch;
class Switch extends FinalSwitch {
Case getCase(int index) { result = super.getCase(index) }
Stmt getStmt(int index) { result = this.(CS::SwitchStmt).getStmt(index) }
}
final private class FinalCase = CS::Case;
class Case extends FinalCase {
AstNode getAPattern() { result = this.getPattern() }
Expr getGuard() { result = this.getCondition() }
AstNode getBody() { result = super.getBody() }
}
class DefaultCase extends Case instanceof CS::DefaultCase { }
class ConditionalExpr = CS::ConditionalExpr;
class BinaryExpr = CS::BinaryOperation;
class LogicalAndExpr = CS::LogicalAndExpr;
class LogicalOrExpr = CS::LogicalOrExpr;
class NullCoalescingExpr = CS::NullCoalescingExpr;
class UnaryExpr = CS::UnaryOperation;
class LogicalNotExpr = CS::LogicalNotExpr;
class Assignment = CS::Assignment;
class AssignExpr = CS::AssignExpr;
class CompoundAssignment = CS::AssignOperation;
class AssignLogicalAndExpr extends CompoundAssignment {
AssignLogicalAndExpr() { none() }
}
class AssignLogicalOrExpr extends CompoundAssignment {
AssignLogicalOrExpr() { none() }
}
class AssignNullCoalescingExpr = CS::AssignCoalesceExpr;
final private class FinalBoolLiteral = CS::BoolLiteral;
class BooleanLiteral extends FinalBoolLiteral {
boolean getValue() { result = this.getBoolValue() }
}
final private class FinalIsExpr = CS::IsExpr;
class PatternMatchExpr extends FinalIsExpr {
AstNode getPattern() { result = super.getPattern() }
}
}
/**
* A compilation.
*
* Unlike the standard `Compilation` class, this class also supports buildless
* extraction.
*/
private newtype TCompilationExt =
TCompilation(Compilation c) { not extractionIsStandalone() } or
TBuildless() { extractionIsStandalone() }
private class CompilationExt extends TCompilationExt {
string toString() {
exists(Compilation c |
this = TCompilation(c) and
result = c.toString()
)
or
this = TBuildless() and result = "buildless compilation"
}
}
/** Gets the compilation that source file `f` belongs to. */
private CompilationExt getCompilation(File f) {
exists(Compilation c |
f = c.getAFileCompiled() and
result = TCompilation(c)
)
or
result = TBuildless()
}
private module Initializers {
private import semmle.code.csharp.ExprOrStmtParent as ExprOrStmtParent
/**
* The `expr_parent_top_level_adjusted()` relation restricted to exclude relations
* between properties and their getters' expression bodies in properties such as
* `int P => 0`.
*
* This is in order to only associate the expression body with one CFG scope, namely
* the getter (and not the declaration itself).
*/
private predicate expr_parent_top_level_adjusted2(
Expr child, int i, @top_level_exprorstmt_parent parent
) {
ExprOrStmtParent::expr_parent_top_level_adjusted(child, i, parent) and
not exists(Getter g |
g.getDeclaration() = parent and
i = 0
)
}
/**
* Holds if `init` is a static member initializer and `staticCtor` is the
* static constructor in the same declaring type. Hence, `staticCtor` can be
* considered to execute `init` prior to the execution of its body.
*/
predicate staticMemberInitializer(Constructor staticCtor, Expr init) {
exists(Assignable a |
a.(Modifiable).isStatic() and
expr_parent_top_level_adjusted2(init, _, a) and
a.getDeclaringType() = staticCtor.getDeclaringType() and
staticCtor.isStatic()
)
}
/**
* Gets the `i`th static member initializer expression for static constructor `staticCtor`.
*/
Expr initializedStaticMemberOrder(Constructor staticCtor, int i) {
result =
rank[i + 1](Expr init, Location l, string filepath, int startline, int startcolumn |
staticMemberInitializer(staticCtor, init) and
l = init.getLocation() and
l.hasLocationInfo(filepath, startline, startcolumn, _, _)
|
init order by startline, startcolumn, filepath
)
}
/**
* Gets the `i`th member initializer expression for object initializer method `obinit`.
*/
AssignExpr initializedInstanceMemberOrder(ObjectInitMethod obinit, int i) {
result =
rank[i + 1](AssignExpr ae0, Location l, string filepath, int startline, int startcolumn |
obinit.initializes(ae0) and
l = ae0.getLocation() and
l.hasLocationInfo(filepath, startline, startcolumn, _, _)
|
ae0 order by startline, startcolumn, filepath
)
}
}
private module Exceptions {
private import semmle.code.csharp.commons.Assertions
private class Overflowable extends UnaryOperation {
Overflowable() {
not this instanceof UnaryBitwiseOperation and
this.getType() instanceof IntegralType
}
}
/** Holds if `cfe` is a control flow element that may throw an exception. */
predicate mayThrowException(ControlFlowElement cfe) {
cfe.(TriedControlFlowElement).mayThrowException()
or
cfe instanceof Assertion
}
/** A control flow element that is inside a `try` block. */
private class TriedControlFlowElement extends ControlFlowElement {
TriedControlFlowElement() {
this = any(TryStmt try).getATriedElement() and
not this instanceof NonReturning::NonReturningCall
}
/**
* Holds if this element may potentially throw an exception.
*/
predicate mayThrowException() {
this instanceof Overflowable
or
this.(CastExpr).getType() instanceof IntegralType
or
invalidCastCandidate(this)
or
this instanceof Call
or
this =
any(MemberAccess ma |
not ma.isConditional() and
ma.getQualifier() = any(Expr e | not e instanceof TypeAccess)
)
or
this instanceof DelegateCreation
or
this instanceof ArrayCreation
or
this =
any(AddOperation ae |
ae.getType() instanceof StringType
or
ae.getType() instanceof IntegralType
)
or
this = any(SubOperation se | se.getType() instanceof IntegralType)
or
this = any(MulOperation me | me.getType() instanceof IntegralType)
or
this = any(DivOperation de | not de.getDenominator().getValue().toFloat() != 0)
or
this instanceof RemOperation
or
this instanceof DynamicExpr
}
}
pragma[nomagic]
private ValueOrRefType getACastExprBaseType(CastExpr ce) {
result = ce.getType().(ValueOrRefType).getABaseType()
or
result = getACastExprBaseType(ce).getABaseType()
}
pragma[nomagic]
private predicate invalidCastCandidate(CastExpr ce) {
ce.getExpr().getType() = getACastExprBaseType(ce)
}
}
private module Input implements InputSig1, InputSig2 {
predicate cfgCachedStageRef() { CfgCachedStage::ref() }
predicate catchAll(Ast::CatchClause catch) { catch instanceof GeneralCatchClause }
predicate matchAll(Ast::Case c) { c instanceof DefaultCase or c.(SwitchCaseExpr).matchesAll() }
private newtype TLabel =
TLblGoto(string label) { any(GotoLabelStmt goto).getLabel() = label } or
TLblSwitchCase(string value) { any(GotoCaseStmt goto).getLabel() = value } or
TLblSwitchDefault()
class Label extends TLabel {
string toString() {
this = TLblGoto(result)
or
this = TLblSwitchCase(result)
or
this = TLblSwitchDefault() and result = "default"
}
}
predicate hasLabel(Ast::AstNode n, Label l) {
l = TLblGoto(n.(GotoLabelStmt).getLabel())
or
l = TLblSwitchCase(n.(GotoCaseStmt).getLabel())
or
l = TLblSwitchDefault() and n instanceof GotoDefaultStmt
or
l = TLblGoto(n.(LabelStmt).getLabel())
}
class CallableBodyPartContext = CompilationExt;
pragma[nomagic]
Ast::AstNode callableGetBodyPart(Callable c, CallableBodyPartContext ctx, int index) {
not Ast::skipControlFlow(result) and
ctx = getCompilation(result.getFile()) and
(
result = Initializers::initializedInstanceMemberOrder(c, index)
or
result = Initializers::initializedStaticMemberOrder(c, index)
or
exists(Constructor ctor, int i, int staticMembers |
c = ctor and
staticMembers = count(Expr init | Initializers::staticMemberInitializer(ctor, init)) and
index = staticMembers + i + 1
|
i = 0 and result = ctor.getObjectInitializerCall()
or
i = 1 and result = ctor.getInitializer()
or
i = 2 and result = ctor.getBody()
)
)
}
private Expr getQualifier(QualifiableExpr qe) {
result = qe.getQualifier() or
result = qe.(ExtensionMethodCall).getArgument(0)
}
predicate inConditionalContext(Ast::AstNode n, ConditionKind kind) {
kind.isNullness() and
exists(QualifiableExpr qe | qe.isConditional() | n = getQualifier(qe))
}
predicate postOrInOrder(Ast::AstNode n) { n instanceof YieldStmt or n instanceof Call }
predicate beginAbruptCompletion(
Ast::AstNode ast, PreControlFlowNode n, AbruptCompletion c, boolean always
) {
// `yield break` behaves like a return statement
ast instanceof YieldBreakStmt and
n.isIn(ast) and
c.asSimpleAbruptCompletion() instanceof ReturnSuccessor and
always = true
or
Exceptions::mayThrowException(ast) and
n.isIn(ast) and
c.asSimpleAbruptCompletion() instanceof ExceptionSuccessor and
always = false
or
ast instanceof NonReturning::NonReturningCall and
n.isIn(ast) and
c.asSimpleAbruptCompletion() instanceof ExceptionSuccessor and
always = true
}
predicate endAbruptCompletion(Ast::AstNode ast, PreControlFlowNode n, AbruptCompletion c) {
exists(SwitchStmt switch, Label l, Ast::Case case |
ast.(Stmt).getParent() = switch and
c.getSuccessorType() instanceof GotoSuccessor and
c.hasLabel(l) and
n.isAfterValue(case, any(MatchingSuccessor t | t.getValue() = true))
|
exists(string value, ConstCase cc |
l = TLblSwitchCase(value) and
switch.getAConstCase() = cc and
cc.getLabel() = value and
cc = case
)
or
l = TLblSwitchDefault() and switch.getDefaultCase() = case
)
}
predicate step(PreControlFlowNode n1, PreControlFlowNode n2) {
exists(QualifiableExpr qe | qe.isConditional() |
n1.isBefore(qe) and n2.isBefore(getQualifier(qe))
or
exists(NullnessSuccessor t | n1.isAfterValue(getQualifier(qe), t) |
if t.isNull()
then (
// if `q` is null in `q?.f = x` then the assignment is skipped. This
// holds for both regular, compound, and null-coalescing assignments.
// On the other hand, the CFG definition for the assignment can treat
// the LHS the same regardless of whether it's a conditionally
// qualified access or not, as it just connects to the "before" and
// "after" nodes of the LHS, and the "after" node is skipped in this
// case.
exists(AssignableDefinition def |
def.getTargetAccess() = qe and
n2.isAfterValue(def.getExpr(), t)
)
or
not qe instanceof AssignableWrite and
n2.isAfterValue(qe, t)
) else (
n2.isBefore(Ast::getChild(qe, 0))
or
n2.isIn(qe) and not exists(Ast::getChild(qe, 0))
)
)
or
exists(int i | i >= 0 and n1.isAfter(Ast::getChild(qe, i)) |
n2.isBefore(Ast::getChild(qe, i + 1))
or
not exists(Ast::getChild(qe, i + 1)) and n2.isIn(qe)
)
or
n1.isIn(qe) and n2.isAfter(qe) and not beginAbruptCompletion(qe, n1, _, true)
)
or
exists(ObjectCreation oc |
n1.isBefore(oc) and n2.isBefore(oc.getArgument(0))
or
n1.isBefore(oc) and n2.isIn(oc) and not exists(oc.getAnArgument())
or
exists(int i | n1.isAfter(oc.getArgument(i)) |
n2.isBefore(oc.getArgument(i + 1))
or
not exists(oc.getArgument(i + 1)) and n2.isIn(oc)
)
or
n1.isIn(oc) and n2.isBefore(oc.getInitializer())
or
n1.isIn(oc) and n2.isAfter(oc) and not exists(oc.getInitializer())
or
n1.isAfter(oc.getInitializer()) and n2.isAfter(oc)
)
}
}
/** Provides different types of control flow nodes. */
module ControlFlowNodes {
/**
* A node for a control flow element, that is, an expression or a statement.
*
* Each control flow element maps to zero or one `ElementNode`s: zero when
* the element is in unreachable (dead) code, and otherwise one.
*/
class ElementNode extends ControlFlowNode {
ElementNode() { exists(this.asExpr()) or exists(this.asStmt()) }
}
/** A control-flow node for an expression. */
class ExprNode extends ElementNode {
Expr e;
ExprNode() { e = this.asExpr() }
/** Gets the expression that this control-flow node belongs to. */
Expr getExpr() { result = e }
/** Gets the value of this expression node, if any. */
string getValue() { result = e.getValue() }
/** Gets the type of this expression node. */
Type getType() { result = e.getType() }
}
}

View File

@@ -4,16 +4,13 @@
import csharp
private import codeql.controlflow.ControlFlowReachability
private import semmle.code.csharp.controlflow.BasicBlocks
private import semmle.code.csharp.controlflow.Guards as Guards
private import semmle.code.csharp.ExprOrStmtParent
private module ControlFlowInput implements
InputSig<Location, ControlFlow::Node, ControlFlow::BasicBlock>
{
private module ControlFlowInput implements InputSig<Location, ControlFlowNode, BasicBlock> {
private import csharp as CS
AstNode getEnclosingAstNode(ControlFlow::Node node) {
AstNode getEnclosingAstNode(ControlFlowNode node) {
node.getAstNode() = result
or
not exists(node.getAstNode()) and result = node.getEnclosingCallable()

View File

@@ -7,20 +7,16 @@ private import ControlFlow
private import semmle.code.csharp.commons.Assertions
private import semmle.code.csharp.commons.ComparisonTest
private import semmle.code.csharp.commons.StructuralComparison as SC
private import semmle.code.csharp.controlflow.BasicBlocks
private import semmle.code.csharp.controlflow.internal.Completion
private import semmle.code.csharp.frameworks.System
private import semmle.code.csharp.frameworks.system.Linq
private import semmle.code.csharp.frameworks.system.Collections
private import semmle.code.csharp.frameworks.system.collections.Generic
private import codeql.controlflow.Guards as SharedGuards
private module GuardsInput implements
SharedGuards::InputSig<Location, ControlFlow::Node, ControlFlow::BasicBlock>
{
private module GuardsInput implements SharedGuards::InputSig<Location, ControlFlowNode, BasicBlock> {
private import csharp as CS
class NormalExitNode = ControlFlow::Nodes::NormalExitNode;
class NormalExitNode = ControlFlow::NormalExitNode;
class AstNode = ControlFlowElement;
@@ -60,25 +56,16 @@ private module GuardsInput implements
override boolean asBooleanValue() { boolConst(this, result) }
}
private predicate intConst(Expr e, int i) {
e.getValue().toInt() = i and
(
e.getType() instanceof Enum
or
e.getType() instanceof IntegralType
)
}
private class IntegerConstant extends ConstantExpr {
IntegerConstant() { intConst(this, _) }
IntegerConstant() { exists(this.getIntValue()) }
override int asIntegerValue() { intConst(this, result) }
override int asIntegerValue() { result = this.getIntValue() }
}
private class EnumConst extends ConstantExpr {
EnumConst() { this.getType() instanceof Enum and this.hasValue() }
override int asIntegerValue() { result = this.getValue().toInt() }
override int asIntegerValue() { result = this.getIntValue() }
}
private class StringConstant extends ConstantExpr instanceof StringLiteral {
@@ -96,21 +83,14 @@ private module GuardsInput implements
ConstantExpr asConstantCase() { super.getPattern() = result }
private predicate hasEdge(BasicBlock bb1, BasicBlock bb2, MatchingCompletion c) {
exists(PatternExpr pe |
super.getPattern() = pe and
c.isValidFor(pe) and
bb1.getLastNode() = pe.getAControlFlowNode() and
bb1.getASuccessor(c.getAMatchingSuccessorType()) = bb2
)
}
predicate matchEdge(BasicBlock bb1, BasicBlock bb2) {
exists(MatchingCompletion c | this.hasEdge(bb1, bb2, c) and c.isMatch())
bb1.getASuccessor(any(MatchingSuccessor s | s.getValue() = true)) = bb2 and
bb1.getLastNode() = AstNode.super.getControlFlowNode()
}
predicate nonMatchEdge(BasicBlock bb1, BasicBlock bb2) {
exists(MatchingCompletion c | this.hasEdge(bb1, bb2, c) and c.isNonMatch())
bb1.getASuccessor(any(MatchingSuccessor s | s.getValue() = false)) = bb2 and
bb1.getLastNode() = AstNode.super.getControlFlowNode()
}
}
@@ -136,7 +116,7 @@ private module GuardsInput implements
IdExpr() { this instanceof AssignExpr or this instanceof CastExpr }
Expr getEqualChildExpr() {
result = this.(AssignExpr).getRValue()
result = this.(AssignExpr).getRightOperand()
or
result = this.(CastExpr).getExpr()
}
@@ -322,7 +302,7 @@ class Guard extends Guards::Guard {
* In case `cfn` or `sub` access an SSA variable in their left-most qualifier, then
* so must the other (accessing the same SSA variable).
*/
predicate controlsNode(ControlFlow::Nodes::ElementNode cfn, AccessOrCallExpr sub, GuardValue v) {
predicate controlsNode(ControlFlowNodes::ElementNode cfn, AccessOrCallExpr sub, GuardValue v) {
isGuardedByNode(cfn, this, sub, v)
}
@@ -332,7 +312,7 @@ class Guard extends Guards::Guard {
* Note: This predicate is inlined.
*/
pragma[inline]
predicate controlsNode(ControlFlow::Nodes::ElementNode cfn, GuardValue v) {
predicate controlsNode(ControlFlowNodes::ElementNode cfn, GuardValue v) {
guardControls(this, cfn.getBasicBlock(), v)
}
@@ -450,7 +430,8 @@ class DereferenceableExpr extends Expr {
predicate guardSuggestsMaybeNull(Guards::Guard guard) {
not nonNullValueImplied(this) and
(
exists(NullnessCompletion c | c.isValidFor(this) and c.isNull() and guard = this)
exists(guard.getControlFlowNode().getASuccessor(any(NullnessSuccessor n | n.isNull()))) and
guard = this
or
LogicInput::additionalNullCheck(guard, _, this, true)
or
@@ -517,35 +498,35 @@ class EnumerableCollectionExpr extends Expr {
|
// x.Length == 0
ct.getComparisonKind().isEquality() and
ct.getAnArgument().getValue().toInt() = 0 and
ct.getAnArgument().getIntValue() = 0 and
branch = isEmpty
or
// x.Length == k, k > 0
ct.getComparisonKind().isEquality() and
ct.getAnArgument().getValue().toInt() > 0 and
ct.getAnArgument().getIntValue() > 0 and
branch = true and
isEmpty = false
or
// x.Length != 0
ct.getComparisonKind().isInequality() and
ct.getAnArgument().getValue().toInt() = 0 and
ct.getAnArgument().getIntValue() = 0 and
branch = isEmpty.booleanNot()
or
// x.Length != k, k != 0
ct.getComparisonKind().isInequality() and
ct.getAnArgument().getValue().toInt() != 0 and
ct.getAnArgument().getIntValue() != 0 and
branch = false and
isEmpty = false
or
// x.Length > k, k >= 0
ct.getComparisonKind().isLessThan() and
ct.getFirstArgument().getValue().toInt() >= 0 and
ct.getFirstArgument().getIntValue() >= 0 and
branch = true and
isEmpty = false
or
// x.Length >= k, k > 0
ct.getComparisonKind().isLessThanEquals() and
ct.getFirstArgument().getValue().toInt() > 0 and
ct.getFirstArgument().getIntValue() > 0 and
branch = true and
isEmpty = false
)
@@ -605,7 +586,7 @@ class AccessOrCallExpr extends Expr {
* An expression can have more than one SSA qualifier in the presence
* of control flow splitting.
*/
Ssa::Definition getAnSsaQualifier(ControlFlow::Node cfn) { result = getAnSsaQualifier(this, cfn) }
Ssa::Definition getAnSsaQualifier(ControlFlowNode cfn) { result = getAnSsaQualifier(this, cfn) }
}
private Declaration getDeclarationTarget(Expr e) {
@@ -613,14 +594,14 @@ private Declaration getDeclarationTarget(Expr e) {
result = e.(Call).getTarget()
}
private Ssa::Definition getAnSsaQualifier(Expr e, ControlFlow::Node cfn) {
private Ssa::Definition getAnSsaQualifier(Expr e, ControlFlowNode cfn) {
e = getATrackedAccess(result, cfn)
or
not e = getATrackedAccess(_, _) and
result = getAnSsaQualifier(e.(QualifiableExpr).getQualifier(), cfn)
}
private AssignableAccess getATrackedAccess(Ssa::Definition def, ControlFlow::Node cfn) {
private AssignableAccess getATrackedAccess(Ssa::Definition def, ControlFlowNode cfn) {
result = def.getAReadAtNode(cfn)
or
result = def.(Ssa::ExplicitDefinition).getADefinition().getTargetAccess() and
@@ -729,7 +710,7 @@ class GuardedExpr extends AccessOrCallExpr {
* In the example above, the node for `x.ToString()` is null-guarded in the
* split `b == true`, but not in the split `b == false`.
*/
class GuardedControlFlowNode extends ControlFlow::Nodes::ElementNode {
class GuardedControlFlowNode extends ControlFlowNodes::ElementNode {
private Guard g;
private AccessOrCallExpr sub0;
private GuardValue v0;
@@ -785,7 +766,7 @@ class GuardedDataFlowNode extends DataFlow::ExprNode {
private GuardValue v0;
GuardedDataFlowNode() {
exists(ControlFlow::Nodes::ElementNode cfn | exists(this.getExprAtNode(cfn)) |
exists(ControlFlowNodes::ElementNode cfn | exists(this.getExprAtNode(cfn)) |
g.controlsNode(cfn, sub0, v0)
)
}
@@ -836,7 +817,7 @@ module Internal {
/** Holds if expression `e2` is a `null` value whenever `e1` is. */
predicate nullValueImpliedUnary(Expr e1, Expr e2) {
e1 = e2.(AssignExpr).getRValue()
e1 = e2.(AssignExpr).getRightOperand()
or
e1 = e2.(Cast).getExpr()
or
@@ -923,7 +904,7 @@ module Internal {
/** Holds if expression `e2` is a non-`null` value whenever `e1` is. */
predicate nonNullValueImpliedUnary(Expr e1, Expr e2) {
e1 = e2.(CastExpr).getExpr() or
e1 = e2.(AssignExpr).getRValue() or
e1 = e2.(AssignExpr).getRightOperand() or
e1 = e2.(NullCoalescingOperation).getAnOperand()
}
@@ -1083,7 +1064,7 @@ module Internal {
candidateAux(x, d, bb) and
y =
any(AccessOrCallExpr e |
e.getAControlFlowNode().getBasicBlock() = bb and
e.getControlFlowNode().getBasicBlock() = bb and
e.getTarget() = d
)
)
@@ -1115,11 +1096,11 @@ module Internal {
pragma[nomagic]
private predicate nodeIsGuardedBySameSubExpr0(
ControlFlow::Node guardedCfn, BasicBlock guardedBB, AccessOrCallExpr guarded, Guard g,
ControlFlowNode guardedCfn, BasicBlock guardedBB, AccessOrCallExpr guarded, Guard g,
AccessOrCallExpr sub, GuardValue v
) {
Stages::GuardsStage::forceCachingInSameStage() and
guardedCfn = guarded.getAControlFlowNode() and
guardedCfn = guarded.getControlFlowNode() and
guardedBB = guardedCfn.getBasicBlock() and
guardControls(g, guardedBB, v) and
guardControlsSubSame(g, guardedBB, sub) and
@@ -1128,7 +1109,7 @@ module Internal {
pragma[nomagic]
private predicate nodeIsGuardedBySameSubExpr(
ControlFlow::Node guardedCfn, BasicBlock guardedBB, AccessOrCallExpr guarded, Guard g,
ControlFlowNode guardedCfn, BasicBlock guardedBB, AccessOrCallExpr guarded, Guard g,
AccessOrCallExpr sub, GuardValue v
) {
nodeIsGuardedBySameSubExpr0(guardedCfn, guardedBB, guarded, g, sub, v) and
@@ -1137,8 +1118,8 @@ module Internal {
pragma[nomagic]
private predicate nodeIsGuardedBySameSubExprSsaDef0(
ControlFlow::Node cfn, BasicBlock guardedBB, AccessOrCallExpr guarded, Guard g,
ControlFlow::Node subCfn, BasicBlock subCfnBB, AccessOrCallExpr sub, GuardValue v,
ControlFlowNode cfn, BasicBlock guardedBB, AccessOrCallExpr guarded, Guard g,
ControlFlowNode subCfn, BasicBlock subCfnBB, AccessOrCallExpr sub, GuardValue v,
Ssa::Definition def
) {
nodeIsGuardedBySameSubExpr(cfn, guardedBB, guarded, g, sub, v) and
@@ -1148,7 +1129,7 @@ module Internal {
pragma[nomagic]
private predicate nodeIsGuardedBySameSubExprSsaDef(
ControlFlow::Node guardedCfn, AccessOrCallExpr guarded, Guard g, ControlFlow::Node subCfn,
ControlFlowNode guardedCfn, AccessOrCallExpr guarded, Guard g, ControlFlowNode subCfn,
AccessOrCallExpr sub, GuardValue v, Ssa::Definition def
) {
exists(BasicBlock guardedBB, BasicBlock subCfnBB |
@@ -1162,15 +1143,13 @@ module Internal {
private predicate isGuardedByExpr0(
AccessOrCallExpr guarded, Guard g, AccessOrCallExpr sub, GuardValue v
) {
forex(ControlFlow::Node cfn | cfn = guarded.getAControlFlowNode() |
nodeIsGuardedBySameSubExpr(cfn, _, guarded, g, sub, v)
)
nodeIsGuardedBySameSubExpr(guarded.getControlFlowNode(), _, guarded, g, sub, v)
}
cached
predicate isGuardedByExpr(AccessOrCallExpr guarded, Guard g, AccessOrCallExpr sub, GuardValue v) {
isGuardedByExpr0(guarded, g, sub, v) and
forall(ControlFlow::Node subCfn, Ssa::Definition def |
forall(ControlFlowNode subCfn, Ssa::Definition def |
nodeIsGuardedBySameSubExprSsaDef(_, guarded, g, subCfn, sub, v, def)
|
def = guarded.getAnSsaQualifier(_)
@@ -1179,17 +1158,14 @@ module Internal {
cached
predicate isGuardedByNode(
ControlFlow::Nodes::ElementNode guarded, Guard g, AccessOrCallExpr sub, GuardValue v
ControlFlowNodes::ElementNode guarded, Guard g, AccessOrCallExpr sub, GuardValue v
) {
nodeIsGuardedBySameSubExpr(guarded, _, _, g, sub, v) and
forall(ControlFlow::Node subCfn, Ssa::Definition def |
forall(ControlFlowNode subCfn, Ssa::Definition def |
nodeIsGuardedBySameSubExprSsaDef(guarded, _, g, subCfn, sub, v, def)
|
def =
guarded
.getAstNode()
.(AccessOrCallExpr)
.getAnSsaQualifier(guarded.getBasicBlock().getANode())
guarded.asExpr().(AccessOrCallExpr).getAnSsaQualifier(guarded.getBasicBlock().getANode())
)
}
}

View File

@@ -1,896 +0,0 @@
/**
* INTERNAL: Do not use.
*
* Provides classes representing control flow completions.
*
* A completion represents how a statement or expression terminates.
*
* There are six kinds of completions: normal completion,
* `return` completion, `break` completion, `continue` completion,
* `goto` completion, and `throw` completion.
*
* Normal completions are further subdivided into Boolean completions and all
* other normal completions. A Boolean completion adds the information that the
* expression terminated with the given boolean value due to a subexpression
* terminating with the other given Boolean value. This is only relevant for
* conditional contexts in which the value controls the control-flow successor.
*
* Goto successors are further subdivided into label gotos, case gotos, and
* default gotos.
*/
import csharp
private import semmle.code.csharp.commons.Assertions
private import semmle.code.csharp.commons.Constants
private import semmle.code.csharp.frameworks.System
private import ControlFlowGraphImpl
private import NonReturning
private import SuccessorType
private newtype TCompletion =
TSimpleCompletion() or
TBooleanCompletion(boolean b) { b = true or b = false } or
TNullnessCompletion(boolean isNull) { isNull = true or isNull = false } or
TMatchingCompletion(boolean isMatch) { isMatch = true or isMatch = false } or
TEmptinessCompletion(boolean isEmpty) { isEmpty = true or isEmpty = false } or
TReturnCompletion() or
TBreakCompletion() or
TContinueCompletion() or
TGotoCompletion(string label) { label = any(GotoStmt gs).getLabel() } or
TThrowCompletion(ExceptionClass ec) or
TExitCompletion() or
TNestedCompletion(Completion inner, Completion outer, int nestLevel) {
inner = TBreakCompletion() and
outer instanceof NonNestedNormalCompletion and
nestLevel = 0
or
inner instanceof NormalCompletion and
nestedFinallyCompletion(outer, nestLevel)
}
pragma[nomagic]
private int getAFinallyNestLevel() { result = any(Statements::TryStmtTree t).nestLevel() }
pragma[nomagic]
private predicate nestedFinallyCompletion(Completion outer, int nestLevel) {
(
outer = TReturnCompletion()
or
outer = TBreakCompletion()
or
outer = TContinueCompletion()
or
outer = TGotoCompletion(_)
or
outer = TThrowCompletion(_)
or
outer = TExitCompletion()
) and
nestLevel = getAFinallyNestLevel()
}
pragma[noinline]
private predicate completionIsValidForStmt(Stmt s, Completion c) {
s instanceof BreakStmt and
c = TBreakCompletion()
or
s instanceof ContinueStmt and
c = TContinueCompletion()
or
s instanceof GotoStmt and
c = TGotoCompletion(s.(GotoStmt).getLabel())
or
s instanceof ReturnStmt and
c = TReturnCompletion()
or
s instanceof YieldBreakStmt and
// `yield break` behaves like a return statement
c = TReturnCompletion()
or
mustHaveEmptinessCompletion(s) and
c = TEmptinessCompletion(_)
}
/**
* A completion of a statement or an expression.
*/
abstract class Completion extends TCompletion {
/**
* Holds if this completion is valid for control flow element `cfe`.
*
* If `cfe` is part of a `try` statement and `cfe` may throw an exception, this
* completion can be a throw completion.
*
* If `cfe` is used in a Boolean context, this completion is a Boolean completion,
* otherwise it is a normal non-Boolean completion.
*/
predicate isValidFor(ControlFlowElement cfe) {
this = cfe.(NonReturningCall).getACompletion()
or
this = TThrowCompletion(cfe.(TriedControlFlowElement).getAThrownException())
or
cfe instanceof ThrowElement and
this = TThrowCompletion(cfe.(ThrowElement).getThrownExceptionType())
or
this = assertionCompletion(cfe, _)
or
completionIsValidForStmt(cfe, this)
or
mustHaveBooleanCompletion(cfe) and
(
exists(boolean value | isBooleanConstant(cfe, value) | this = TBooleanCompletion(value))
or
not isBooleanConstant(cfe, _) and
this = TBooleanCompletion(_)
or
// Corner case: In `if (x ?? y) { ... }`, `x` must have both a `true`
// completion, a `false` completion, and a `null` completion (but not a
// non-`null` completion)
mustHaveNullnessCompletion(cfe) and
this = TNullnessCompletion(true)
)
or
mustHaveNullnessCompletion(cfe) and
not mustHaveBooleanCompletion(cfe) and
(
exists(boolean value | isNullnessConstant(cfe, value) | this = TNullnessCompletion(value))
or
not isNullnessConstant(cfe, _) and
this = TNullnessCompletion(_)
)
or
mustHaveMatchingCompletion(cfe) and
(
exists(boolean value | isMatchingConstant(cfe, value) | this = TMatchingCompletion(value))
or
not isMatchingConstant(cfe, _) and
this = TMatchingCompletion(_)
)
or
not cfe instanceof NonReturningCall and
not cfe instanceof ThrowElement and
not cfe instanceof BreakStmt and
not cfe instanceof ContinueStmt and
not cfe instanceof GotoStmt and
not cfe instanceof ReturnStmt and
not cfe instanceof YieldBreakStmt and
not mustHaveBooleanCompletion(cfe) and
not mustHaveNullnessCompletion(cfe) and
not mustHaveMatchingCompletion(cfe) and
not mustHaveEmptinessCompletion(cfe) and
this = TSimpleCompletion()
}
/**
* Holds if this completion will continue a loop when it is the completion
* of a loop body.
*/
predicate continuesLoop() {
this instanceof NormalCompletion or
this instanceof ContinueCompletion
}
/**
* Gets the inner completion. This is either the inner completion,
* when the completion is nested, or the completion itself.
*/
Completion getInnerCompletion() { result = this }
/**
* Gets the outer completion. This is either the outer completion,
* when the completion is nested, or the completion itself.
*/
Completion getOuterCompletion() { result = this }
/** Gets a successor type that matches this completion. */
abstract SuccessorType getAMatchingSuccessorType();
/** Gets a textual representation of this completion. */
abstract string toString();
}
/** Holds if expression `e` has the Boolean constant value `value`. */
private predicate isBooleanConstant(Expr e, boolean value) {
mustHaveBooleanCompletion(e) and
(
e.getValue() = "true" and
value = true
or
e.getValue() = "false" and
value = false
or
isConstantComparison(e, value)
or
exists(Method m, Call c, Expr expr |
m = any(SystemStringClass s).getIsNullOrEmptyMethod() and
c.getTarget() = m and
e = c and
expr = c.getArgument(0) and
expr.hasValue() and
if expr.getValue().length() > 0 and not expr instanceof NullLiteral
then value = false
else value = true
)
)
}
/**
* Holds if expression `e` is constantly `null` (`value = true`) or constantly
* non-`null` (`value = false`).
*/
private predicate isNullnessConstant(Expr e, boolean value) {
mustHaveNullnessCompletion(e) and
exists(Expr stripped | stripped = e.stripCasts() |
stripped.getType() =
any(ValueType t |
not t instanceof NullableType and
// Extractor bug: the type of `x?.Length` is reported as `int`, but it should
// be `int?`
not getQualifier*(stripped).(QualifiableExpr).isConditional()
) and
value = false
or
stripped instanceof NullLiteral and
value = true
or
stripped.hasValue() and
not stripped instanceof NullLiteral and
value = false
)
}
private Expr getQualifier(QualifiableExpr e) {
// `e.getQualifier()` does not work for calls to extension methods
result = e.getChildExpr(-1)
}
pragma[noinline]
private predicate typePatternMustHaveMatchingCompletion(
TypePatternExpr tpe, Type t, Type strippedType
) {
exists(Expr e, Expr stripped | mustHaveMatchingCompletion(e, tpe) |
stripped = e.stripCasts() and
t = tpe.getCheckedType() and
strippedType = stripped.getType() and
not t.containsTypeParameters() and
not strippedType.containsTypeParameters()
)
}
pragma[noinline]
private Type typePatternCommonSubTypeLeft(Type t) {
typePatternMustHaveMatchingCompletion(_, t, _) and
result.isImplicitlyConvertibleTo(t) and
not result instanceof DynamicType
}
pragma[noinline]
private Type typePatternCommonSubTypeRight(Type strippedType) {
typePatternMustHaveMatchingCompletion(_, _, strippedType) and
result.isImplicitlyConvertibleTo(strippedType) and
not result instanceof DynamicType
}
pragma[noinline]
private predicate typePatternCommonSubType(Type t, Type strippedType) {
typePatternCommonSubTypeLeft(t) = typePatternCommonSubTypeRight(strippedType)
}
/**
* Holds if pattern expression `pe` constantly matches (`value = true`) or
* constantly non-matches (`value = false`).
*/
private predicate isMatchingConstant(PatternExpr pe, boolean value) {
exists(Expr e, string exprValue, string patternValue |
mustHaveMatchingCompletion(e, pe) and
exprValue = e.stripCasts().getValue() and
patternValue = pe.getValue() and
if exprValue = patternValue then value = true else value = false
)
or
pe instanceof DiscardPatternExpr and
value = true
or
exists(Type t, Type strippedType |
not t instanceof UnknownType and
not strippedType instanceof UnknownType and
typePatternMustHaveMatchingCompletion(pe, t, strippedType) and
not typePatternCommonSubType(t, strippedType) and
value = false
)
}
private class Overflowable extends UnaryOperation {
Overflowable() {
not this instanceof UnaryBitwiseOperation and
this.getType() instanceof IntegralType
}
}
/** A control flow element that is inside a `try` block. */
private class TriedControlFlowElement extends ControlFlowElement {
TriedControlFlowElement() {
this = any(TryStmt try).getATriedElement() and
not this instanceof NonReturningCall
}
/**
* Gets an exception class that is potentially thrown by this element, if any.
*/
Class getAThrownException() {
this instanceof Overflowable and
result instanceof SystemOverflowExceptionClass
or
this.(CastExpr).getType() instanceof IntegralType and
result instanceof SystemOverflowExceptionClass
or
invalidCastCandidate(this) and
result instanceof SystemInvalidCastExceptionClass
or
this instanceof Call and
result instanceof SystemExceptionClass
or
this =
any(MemberAccess ma |
not ma.isConditional() and
ma.getQualifier() = any(Expr e | not e instanceof TypeAccess) and
result instanceof SystemNullReferenceExceptionClass
)
or
this instanceof DelegateCreation and
result instanceof SystemOutOfMemoryExceptionClass
or
this instanceof ArrayCreation and
result instanceof SystemOutOfMemoryExceptionClass
or
this =
any(AddOperation ae |
ae.getType() instanceof StringType and
result instanceof SystemOutOfMemoryExceptionClass
or
ae.getType() instanceof IntegralType and
result instanceof SystemOverflowExceptionClass
)
or
this =
any(SubOperation se |
se.getType() instanceof IntegralType and
result instanceof SystemOverflowExceptionClass
)
or
this =
any(MulOperation me |
me.getType() instanceof IntegralType and
result instanceof SystemOverflowExceptionClass
)
or
this =
any(DivOperation de |
not de.getDenominator().getValue().toFloat() != 0 and
result instanceof SystemDivideByZeroExceptionClass
)
or
this instanceof RemOperation and
result instanceof SystemDivideByZeroExceptionClass
or
this instanceof DynamicExpr and
result instanceof SystemExceptionClass
}
}
pragma[nomagic]
private ValueOrRefType getACastExprBaseType(CastExpr ce) {
result = ce.getType().(ValueOrRefType).getABaseType()
or
result = getACastExprBaseType(ce).getABaseType()
}
pragma[nomagic]
private predicate invalidCastCandidate(CastExpr ce) {
ce.getExpr().getType() = getACastExprBaseType(ce)
}
/** Gets a valid completion when argument `i` fails in assertion `a`. */
Completion assertionCompletion(Assertion a, int i) {
exists(AssertMethod am | am = a.getAssertMethod() |
if am.getAssertionFailure(i).isExit()
then result = TExitCompletion()
else
exists(Class c |
am.getAssertionFailure(i).isException(c) and
result = TThrowCompletion(c)
)
)
}
/**
* Holds if a normal completion of `e` must be a Boolean completion.
*/
private predicate mustHaveBooleanCompletion(Expr e) {
inBooleanContext(e) and
not e instanceof NonReturningCall
}
/**
* Holds if `e` is used in a Boolean context. That is, whether the value
* that `e` evaluates to determines a true/false branch successor.
*/
private predicate inBooleanContext(Expr e) {
e = any(IfStmt is).getCondition()
or
e = any(LoopStmt ls).getCondition()
or
e = any(Case c).getCondition()
or
e = any(SpecificCatchClause scc).getFilterClause()
or
e = any(LogicalNotExpr lne | inBooleanContext(lne)).getAnOperand()
or
exists(LogicalAndExpr lae |
lae.getLeftOperand() = e
or
inBooleanContext(lae) and
lae.getRightOperand() = e
)
or
exists(LogicalOrExpr lae |
lae.getLeftOperand() = e
or
inBooleanContext(lae) and
lae.getRightOperand() = e
)
or
exists(ConditionalExpr ce |
ce.getCondition() = e
or
inBooleanContext(ce) and
e in [ce.getThen(), ce.getElse()]
)
or
e = any(NullCoalescingOperation nce | inBooleanContext(nce)).getAnOperand()
or
e = any(SwitchExpr se | inBooleanContext(se)).getACase()
or
e = any(SwitchCaseExpr sce | inBooleanContext(sce)).getBody()
}
/**
* Holds if a normal completion of `e` must be a nullness completion.
*/
private predicate mustHaveNullnessCompletion(Expr e) {
inNullnessContext(e) and
not e instanceof NonReturningCall
}
/**
* Holds if `e` is used in a nullness context. That is, whether the value
* that `e` evaluates to determines a `null`/non-`null` branch successor.
*/
private predicate inNullnessContext(Expr e) {
e = any(NullCoalescingOperation nce).getLeftOperand()
or
exists(QualifiableExpr qe | qe.isConditional() | e = qe.getChildExpr(-1))
or
exists(ConditionalExpr ce | inNullnessContext(ce) | (e = ce.getThen() or e = ce.getElse()))
or
exists(NullCoalescingOperation nce | inNullnessContext(nce) | e = nce.getRightOperand())
or
e = any(SwitchExpr se | inNullnessContext(se)).getACase()
or
e = any(SwitchCaseExpr sce | inNullnessContext(sce)).getBody()
}
/**
* Holds if `pe` is the pattern inside case `c`, belonging to `switch` `s`, that
* has the matching completion.
*/
predicate switchMatching(Switch s, Case c, PatternExpr pe) {
s.getACase() = c and
pe = c.getPattern()
}
/**
* Holds if a normal completion of `cfe` must be a matching completion. Thats is,
* whether `cfe` determines a match in a `switch/if` statement or `catch` clause.
*/
private predicate mustHaveMatchingCompletion(ControlFlowElement cfe) {
switchMatching(_, _, cfe)
or
cfe instanceof SpecificCatchClause
or
cfe = any(IsExpr ie | inBooleanContext(ie)).getPattern()
or
cfe = any(RecursivePatternExpr rpe).getAChildExpr()
or
cfe = any(PositionalPatternExpr ppe).getPattern(_)
or
cfe = any(PropertyPatternExpr ppe).getPattern(_)
or
cfe = any(UnaryPatternExpr upe | mustHaveMatchingCompletion(upe)).getPattern()
or
cfe = any(BinaryPatternExpr bpe).getAnOperand()
}
/**
* Holds if `pe` must have a matching completion, and `e` is the expression
* that is being matched.
*/
private predicate mustHaveMatchingCompletion(Expr e, PatternExpr pe) {
exists(Switch s |
switchMatching(s, _, pe) and
e = s.getExpr()
)
or
e = any(IsExpr ie | pe = ie.getPattern()).getExpr() and
mustHaveMatchingCompletion(pe)
or
exists(PatternExpr mid | mustHaveMatchingCompletion(e, mid) |
pe = mid.(UnaryPatternExpr).getPattern()
or
pe = mid.(RecursivePatternExpr).getAChildExpr()
or
pe = mid.(BinaryPatternExpr).getAnOperand()
)
}
/**
* Holds if `cfe` is the element inside foreach statement `fs` that has the emptiness
* completion.
*/
predicate foreachEmptiness(ForeachStmt fs, ControlFlowElement cfe) {
cfe = fs // use `foreach` statement itself to represent the emptiness test
}
/**
* Holds if a normal completion of `cfe` must be an emptiness completion. Thats is,
* whether `cfe` determines whether to execute the body of a `foreach` statement.
*/
private predicate mustHaveEmptinessCompletion(ControlFlowElement cfe) { foreachEmptiness(_, cfe) }
/**
* A completion that represents normal evaluation of a statement or an
* expression.
*/
abstract class NormalCompletion extends Completion { }
abstract private class NonNestedNormalCompletion extends NormalCompletion { }
/** A simple (normal) completion. */
class SimpleCompletion extends NonNestedNormalCompletion, TSimpleCompletion {
override DirectSuccessor getAMatchingSuccessorType() { any() }
override string toString() { result = "normal" }
}
/**
* A completion that represents evaluation of an expression, whose value determines
* the successor. Either a Boolean completion (`BooleanCompletion`), a nullness
* completion (`NullnessCompletion`), a matching completion (`MatchingCompletion`),
* or an emptiness completion (`EmptinessCompletion`).
*/
abstract class ConditionalCompletion extends NonNestedNormalCompletion {
/** Gets the Boolean value of this completion. */
abstract boolean getValue();
/** Gets the dual completion. */
abstract ConditionalCompletion getDual();
}
/**
* A completion that represents evaluation of an expression
* with a Boolean value.
*/
class BooleanCompletion extends ConditionalCompletion {
private boolean value;
BooleanCompletion() { this = TBooleanCompletion(value) }
override boolean getValue() { result = value }
override BooleanCompletion getDual() { result = TBooleanCompletion(value.booleanNot()) }
override BooleanSuccessor getAMatchingSuccessorType() { result.getValue() = value }
override string toString() { result = value.toString() }
}
/** A Boolean `true` completion. */
class TrueCompletion extends BooleanCompletion {
TrueCompletion() { this.getValue() = true }
}
/** A Boolean `false` completion. */
class FalseCompletion extends BooleanCompletion {
FalseCompletion() { this.getValue() = false }
}
/**
* A completion that represents evaluation of an expression that is either
* `null` or non-`null`.
*/
class NullnessCompletion extends ConditionalCompletion, TNullnessCompletion {
private boolean value;
NullnessCompletion() { this = TNullnessCompletion(value) }
/** Holds if the last sub expression of this expression evaluates to `null`. */
predicate isNull() { value = true }
/** Holds if the last sub expression of this expression evaluates to a non-`null` value. */
predicate isNonNull() { value = false }
override boolean getValue() { result = value }
override NullnessCompletion getDual() { result = TNullnessCompletion(value.booleanNot()) }
override NullnessSuccessor getAMatchingSuccessorType() { result.getValue() = value }
override string toString() { if this.isNull() then result = "null" else result = "non-null" }
}
/**
* A completion that represents matching, for example a `case` statement in a
* `switch` statement.
*/
class MatchingCompletion extends ConditionalCompletion, TMatchingCompletion {
private boolean value;
MatchingCompletion() { this = TMatchingCompletion(value) }
/** Holds if there is a match. */
predicate isMatch() { value = true }
/** Holds if there is not a match. */
predicate isNonMatch() { value = false }
override boolean getValue() { result = value }
override MatchingCompletion getDual() { result = TMatchingCompletion(value.booleanNot()) }
override MatchingSuccessor getAMatchingSuccessorType() { result.getValue() = value }
override string toString() { if this.isMatch() then result = "match" else result = "no-match" }
}
/**
* A completion that represents evaluation of an emptiness test, for example
* a test in a `foreach` statement.
*/
class EmptinessCompletion extends ConditionalCompletion, TEmptinessCompletion {
private boolean value;
EmptinessCompletion() { this = TEmptinessCompletion(value) }
/** Holds if the emptiness test evaluates to `true`. */
predicate isEmpty() { value = true }
override boolean getValue() { result = value }
override EmptinessCompletion getDual() { result = TEmptinessCompletion(value.booleanNot()) }
override EmptinessSuccessor getAMatchingSuccessorType() { result.getValue() = value }
override string toString() { if this.isEmpty() then result = "empty" else result = "non-empty" }
}
/**
* A nested completion. For example, in
*
* ```csharp
* void M(bool b1, bool b2)
* {
* try
* {
* if (b1)
* throw new Exception();
* }
* finally
* {
* if (b2)
* System.Console.WriteLine("M called");
* }
* }
* ```
*
* `b2` has an outer throw completion (inherited from `throw new Exception`)
* and an inner `false` completion. `b2` also has a (normal) `true` completion.
*/
class NestedCompletion extends Completion, TNestedCompletion {
Completion inner;
Completion outer;
int nestLevel;
NestedCompletion() { this = TNestedCompletion(inner, outer, nestLevel) }
/** Gets a completion that is compatible with the inner completion. */
Completion getAnInnerCompatibleCompletion() {
result.getOuterCompletion() = this.getInnerCompletion()
}
/** Gets the level of this nested completion. */
int getNestLevel() { result = nestLevel }
override Completion getInnerCompletion() { result = inner }
override Completion getOuterCompletion() { result = outer }
override SuccessorType getAMatchingSuccessorType() { none() }
override string toString() { result = outer + " [" + inner + "] (" + nestLevel + ")" }
}
/**
* A nested completion for a loop that exists with a `break`.
*
* This completion is added for technical reasons only: when a loop
* body can complete with a break completion, the loop itself completes
* normally. However, if we choose `TSimpleCompletion` as the completion
* of the loop, we lose the information that the last element actually
* completed with a break, meaning that the control flow edge out of the
* breaking node cannot be marked with a `break` label.
*
* Example:
*
* ```csharp
* while (...) {
* ...
* break;
* }
* return;
* ```
*
* The `break` on line 3 completes with a `TBreakCompletion`, therefore
* the `while` loop can complete with a `NestedBreakCompletion`, so we
* get an edge `break --break--> return`. (If we instead used a
* `TSimpleCompletion`, we would get a less precise edge
* `break --normal--> return`.)
*/
class NestedBreakCompletion extends NormalCompletion, NestedCompletion {
NestedBreakCompletion() {
inner = TBreakCompletion() and
outer instanceof NonNestedNormalCompletion
}
override BreakCompletion getInnerCompletion() { result = inner }
override NonNestedNormalCompletion getOuterCompletion() { result = outer }
override Completion getAnInnerCompatibleCompletion() {
result = inner and
outer = TSimpleCompletion()
or
result = TNestedCompletion(outer, inner, _)
}
override SuccessorType getAMatchingSuccessorType() {
outer instanceof SimpleCompletion and
result instanceof BreakSuccessor
or
result = outer.(ConditionalCompletion).getAMatchingSuccessorType()
}
}
/**
* A completion that represents evaluation of a statement or an
* expression resulting in a return from a callable.
*/
class ReturnCompletion extends Completion {
ReturnCompletion() {
this = TReturnCompletion() or
this = TNestedCompletion(_, TReturnCompletion(), _)
}
override ReturnSuccessor getAMatchingSuccessorType() { any() }
override string toString() {
// `NestedCompletion` defines `toString()` for the other case
this = TReturnCompletion() and result = "return"
}
}
/**
* A completion that represents evaluation of a statement or an
* expression resulting in a break (in a loop or in a `switch`
* statement).
*/
class BreakCompletion extends Completion {
BreakCompletion() {
this = TBreakCompletion() or
this = TNestedCompletion(_, TBreakCompletion(), _)
}
override BreakSuccessor getAMatchingSuccessorType() { any() }
override string toString() {
// `NestedCompletion` defines `toString()` for the other case
this = TBreakCompletion() and result = "break"
}
}
/**
* A completion that represents evaluation of a statement or an
* expression resulting in a loop continuation (a `continue`
* statement).
*/
class ContinueCompletion extends Completion {
ContinueCompletion() {
this = TContinueCompletion() or
this = TNestedCompletion(_, TContinueCompletion(), _)
}
override ContinueSuccessor getAMatchingSuccessorType() { any() }
override string toString() {
// `NestedCompletion` defines `toString()` for the other case
this = TContinueCompletion() and result = "continue"
}
}
/**
* A completion that represents evaluation of a statement or an
* expression resulting in a `goto` jump.
*/
class GotoCompletion extends Completion {
private string label;
GotoCompletion() {
this = TGotoCompletion(label) or
this = TNestedCompletion(_, TGotoCompletion(label), _)
}
/** Gets the label of the `goto` completion. */
string getLabel() { result = label }
override GotoSuccessor getAMatchingSuccessorType() { any() }
override string toString() {
// `NestedCompletion` defines `toString()` for the other case
this = TGotoCompletion(label) and result = "goto(" + label + ")"
}
}
/**
* A completion that represents evaluation of a statement or an
* expression resulting in a thrown exception.
*/
class ThrowCompletion extends Completion {
private ExceptionClass ec;
ThrowCompletion() {
this = TThrowCompletion(ec) or
this = TNestedCompletion(_, TThrowCompletion(ec), _)
}
/** Gets the type of the exception being thrown. */
ExceptionClass getExceptionClass() { result = ec }
override ExceptionSuccessor getAMatchingSuccessorType() { any() }
override string toString() {
// `NestedCompletion` defines `toString()` for the other case
this = TThrowCompletion(ec) and result = "throw(" + ec + ")"
}
}
/**
* A completion that represents evaluation of a statement or an
* expression resulting in a program exit, for example
* `System.Environment.Exit(0)`.
*
* An exit completion is different from a `return` completion; the former
* exits the whole application, and exists inside `try` statements skip
* `finally` blocks.
*/
class ExitCompletion extends Completion {
ExitCompletion() {
this = TExitCompletion() or
this = TNestedCompletion(_, TExitCompletion(), _)
}
override ExitSuccessor getAMatchingSuccessorType() { any() }
override string toString() {
// `NestedCompletion` defines `toString()` for the other case
this = TExitCompletion() and result = "exit"
}
}

View File

@@ -9,13 +9,9 @@ import csharp
private import semmle.code.csharp.ExprOrStmtParent
private import semmle.code.csharp.commons.Assertions
private import semmle.code.csharp.frameworks.System
private import Completion
/** A call that definitely does not return (conservative analysis). */
abstract class NonReturningCall extends Call {
/** Gets a valid completion for this non-returning call. */
abstract Completion getACompletion();
}
abstract class NonReturningCall extends Call { }
private class ExitingCall extends NonReturningCall {
ExitingCall() {
@@ -23,36 +19,21 @@ private class ExitingCall extends NonReturningCall {
or
this = any(FailingAssertion fa | fa.getAssertionFailure().isExit())
}
override ExitCompletion getACompletion() { not result instanceof NestedCompletion }
}
private class ThrowingCall extends NonReturningCall {
private ThrowCompletion c;
ThrowingCall() {
not c instanceof NestedCompletion and
(
c = this.getTarget().(ThrowingCallable).getACallCompletion()
or
this.(FailingAssertion).getAssertionFailure().isException(c.getExceptionClass())
or
this =
any(MethodCall mc |
mc.getTarget()
.hasFullyQualifiedName("System.Runtime.ExceptionServices", "ExceptionDispatchInfo",
"Throw") and
(
mc.hasNoArguments() and
c.getExceptionClass() instanceof SystemExceptionClass
or
c.getExceptionClass() = mc.getArgument(0).getType()
)
)
)
this.getTarget() instanceof ThrowingCallable
or
this.(FailingAssertion).getAssertionFailure().isException(_)
or
this =
any(MethodCall mc |
mc.getTarget()
.hasFullyQualifiedName("System.Runtime.ExceptionServices", "ExceptionDispatchInfo",
"Throw")
)
}
override ThrowCompletion getACompletion() { result = c }
}
/** Holds if accessor `a` has an auto-implementation. */
@@ -107,44 +88,35 @@ private Stmt getAnExitingStmt() {
private class ThrowingCallable extends NonReturningCallable {
ThrowingCallable() {
forex(ControlFlowElement body | body = this.getBody() | body = getAThrowingElement(_))
forex(ControlFlowElement body | body = this.getBody() | body = getAThrowingElement())
}
/** Gets a valid completion for a call to this throwing callable. */
ThrowCompletion getACallCompletion() { this.getBody() = getAThrowingElement(result) }
}
private predicate directlyThrows(ThrowElement te, ThrowCompletion c) {
c.getExceptionClass() = te.getThrownExceptionType() and
not c instanceof NestedCompletion and
private predicate directlyThrows(ThrowElement te) {
// For stub implementations, there may exist proper implementations that are not seen
// during compilation, so we conservatively rule those out
not isStub(te)
}
private ControlFlowElement getAThrowingElement(ThrowCompletion c) {
c = result.(ThrowingCall).getACompletion()
private ControlFlowElement getAThrowingElement() {
result instanceof ThrowingCall
or
directlyThrows(result, c)
directlyThrows(result)
or
result = getAThrowingStmt(c)
result = getAThrowingStmt()
}
private Stmt getAThrowingStmt(ThrowCompletion c) {
directlyThrows(result, c)
private Stmt getAThrowingStmt() {
directlyThrows(result)
or
result.(ExprStmt).getExpr() = getAThrowingElement(c)
result.(ExprStmt).getExpr() = getAThrowingElement()
or
result.(BlockStmt).getFirstStmt() = getAThrowingStmt(c)
result.(BlockStmt).getFirstStmt() = getAThrowingStmt()
or
exists(IfStmt ifStmt, ThrowCompletion c1, ThrowCompletion c2 |
exists(IfStmt ifStmt |
result = ifStmt and
ifStmt.getThen() = getAThrowingStmt(c1) and
ifStmt.getElse() = getAThrowingStmt(c2)
|
c = c1
or
c = c2
ifStmt.getThen() = getAThrowingStmt() and
ifStmt.getElse() = getAThrowingStmt()
)
}

View File

@@ -1,124 +0,0 @@
/**
* INTERNAL: Do not use.
*
* Provides classes and predicates relevant for splitting the control flow graph.
*/
import csharp
private import Completion as Comp
private import Comp
private import ControlFlowGraphImpl
private import semmle.code.csharp.controlflow.ControlFlowGraph::ControlFlow as Cfg
cached
private module Cached {
private import semmle.code.csharp.Caching
cached
newtype TSplitKind = TConditionalCompletionSplitKind()
cached
newtype TSplit = TConditionalCompletionSplit(ConditionalCompletion c)
}
import Cached
/**
* A split for a control flow element. For example, a tag that determines how to
* continue execution after leaving a `finally` block.
*/
class Split extends TSplit {
/** Gets a textual representation of this split. */
string toString() { none() }
}
module ConditionalCompletionSplitting {
/**
* A split for conditional completions. For example, in
*
* ```csharp
* void M(int i)
* {
* if (x && !y)
* System.Console.WriteLine("true")
* }
* ```
*
* we record whether `x`, `y`, and `!y` evaluate to `true` or `false`, and restrict
* the edges out of `!y` and `x && !y` accordingly.
*/
class ConditionalCompletionSplit extends Split, TConditionalCompletionSplit {
ConditionalCompletion completion;
ConditionalCompletionSplit() { this = TConditionalCompletionSplit(completion) }
ConditionalCompletion getCompletion() { result = completion }
override string toString() { result = completion.toString() }
}
private class ConditionalCompletionSplitKind_ extends SplitKind, TConditionalCompletionSplitKind {
override int getListOrder() { result = 0 }
override predicate isEnabled(AstNode cfe) { this.appliesTo(cfe) }
override string toString() { result = "ConditionalCompletion" }
}
module ConditionalCompletionSplittingInput {
private import Completion as Comp
class ConditionalCompletion = Comp::ConditionalCompletion;
class ConditionalCompletionSplitKind extends ConditionalCompletionSplitKind_, TSplitKind { }
class ConditionalCompletionSplit = ConditionalCompletionSplitting::ConditionalCompletionSplit;
bindingset[parent, parentCompletion]
predicate condPropagateExpr(
AstNode parent, ConditionalCompletion parentCompletion, AstNode child,
ConditionalCompletion childCompletion
) {
child = parent.(LogicalNotExpr).getOperand() and
childCompletion.getDual() = parentCompletion
or
childCompletion = parentCompletion and
(
child = parent.(LogicalAndExpr).getAnOperand()
or
child = parent.(LogicalOrExpr).getAnOperand()
or
parent = any(ConditionalExpr ce | child = [ce.getThen(), ce.getElse()])
or
child = parent.(SwitchExpr).getACase()
or
child = parent.(SwitchCaseExpr).getBody()
or
parent =
any(NullCoalescingOperation nce |
if childCompletion instanceof NullnessCompletion
then child = nce.getRightOperand()
else child = nce.getAnOperand()
)
)
or
child = parent.(NotPatternExpr).getPattern() and
childCompletion.getDual() = parentCompletion
or
child = parent.(IsExpr).getPattern() and
parentCompletion.(BooleanCompletion).getValue() =
childCompletion.(MatchingCompletion).getValue()
or
childCompletion = parentCompletion and
(
child = parent.(AndPatternExpr).getAnOperand()
or
child = parent.(OrPatternExpr).getAnOperand()
or
child = parent.(RecursivePatternExpr).getAChildExpr()
or
child = parent.(PropertyPatternExpr).getPattern(_)
)
}
}
}

View File

@@ -31,7 +31,7 @@ private Expr maybeNullExpr(Expr reason) {
or
result instanceof AsExpr and reason = result
or
result.(AssignExpr).getRValue() = maybeNullExpr(reason)
result.(AssignExpr).getRightOperand() = maybeNullExpr(reason)
or
result.(CastExpr).getExpr() = maybeNullExpr(reason)
or
@@ -126,7 +126,7 @@ private predicate nonNullDef(Ssa::ExplicitDefinition def) {
/**
* Holds if `node` is a dereference `d` of SSA definition `def`.
*/
private predicate dereferenceAt(ControlFlow::Node node, Ssa::Definition def, Dereference d) {
private predicate dereferenceAt(ControlFlowNode node, Ssa::Definition def, Dereference d) {
d = def.getAReadAtNode(node)
}
@@ -192,9 +192,7 @@ private predicate isNullDefaultArgument(Ssa::ImplicitParameterDefinition def, Al
}
/** Holds if `def` is an SSA definition that may be `null`. */
private predicate defMaybeNull(
Ssa::Definition def, ControlFlow::Node node, string msg, Element reason
) {
private predicate defMaybeNull(Ssa::Definition def, ControlFlowNode node, string msg, Element reason) {
not nonNullDef(def) and
(
// A variable compared to `null` might be `null`
@@ -224,7 +222,7 @@ private predicate defMaybeNull(
or
// If the source of a variable is `null` then the variable may be `null`
exists(AssignableDefinition adef | adef = def.(Ssa::ExplicitDefinition).getADefinition() |
adef.getSource() = maybeNullExpr(node.getAstNode()) and
adef.getSource() = maybeNullExpr(node.asExpr()) and
reason = adef.getExpr() and
msg = "because of $@ assignment"
)
@@ -256,19 +254,19 @@ private Ssa::Definition getAnUltimateDefinition(Ssa::Definition def) {
* through an intermediate dereference that always throws a null reference
* exception.
*/
private predicate defReaches(Ssa::Definition def, ControlFlow::Node cfn) {
private predicate defReaches(Ssa::Definition def, ControlFlowNode cfn) {
exists(def.getAFirstReadAtNode(cfn))
or
exists(ControlFlow::Node mid | defReaches(def, mid) |
exists(ControlFlowNode mid | defReaches(def, mid) |
SsaImpl::adjacentReadPairSameVar(_, mid, cfn) and
not mid = any(Dereference d | d.isAlwaysNull(def.getSourceVariable())).getAControlFlowNode()
not mid = any(Dereference d | d.isAlwaysNull(def.getSourceVariable())).getControlFlowNode()
)
}
private module NullnessConfig implements ControlFlowReachability::ConfigSig {
predicate source(ControlFlow::Node node, Ssa::Definition def) { defMaybeNull(def, node, _, _) }
predicate source(ControlFlowNode node, Ssa::Definition def) { defMaybeNull(def, node, _, _) }
predicate sink(ControlFlow::Node node, Ssa::Definition def) {
predicate sink(ControlFlowNode node, Ssa::Definition def) {
exists(Dereference d |
dereferenceAt(node, def, d) and
not d instanceof NonNullExpr
@@ -283,9 +281,7 @@ private module NullnessConfig implements ControlFlowReachability::ConfigSig {
private module NullnessFlow = ControlFlowReachability::Flow<NullnessConfig>;
predicate maybeNullDeref(Dereference d, Ssa::SourceVariable v, string msg, Element reason) {
exists(
Ssa::Definition origin, Ssa::Definition ssa, ControlFlow::Node src, ControlFlow::Node sink
|
exists(Ssa::Definition origin, Ssa::Definition ssa, ControlFlowNode src, ControlFlowNode sink |
defMaybeNull(origin, src, msg, reason) and
NullnessFlow::flow(src, origin, sink, ssa) and
ssa.getSourceVariable() = v and
@@ -388,6 +384,6 @@ class Dereference extends G::DereferenceableExpr {
*/
predicate isFirstAlwaysNull(Ssa::SourceVariable v) {
this.isAlwaysNull(v) and
defReaches(v.getAnSsaDefinition(), this.getAControlFlowNode())
defReaches(v.getAnSsaDefinition(), this.getControlFlowNode())
}
}

View File

@@ -164,10 +164,8 @@ module Ssa {
*/
class Definition extends SsaImpl::Definition {
/** Gets the control flow node of this SSA definition. */
final ControlFlow::Node getControlFlowNode() {
exists(ControlFlow::BasicBlock bb, int i | this.definesAt(_, bb, i) |
result = bb.getNode(0.maximum(i))
)
final ControlFlowNode getControlFlowNode() {
exists(BasicBlock bb, int i | this.definesAt(_, bb, i) | result = bb.getNode(0.maximum(i)))
}
/**
@@ -176,9 +174,7 @@ module Ssa {
* point it is still live, without crossing another SSA definition of the
* same source variable.
*/
final predicate isLiveAtEndOfBlock(ControlFlow::BasicBlock bb) {
SsaImpl::isLiveAtEndOfBlock(this, bb)
}
final predicate isLiveAtEndOfBlock(BasicBlock bb) { SsaImpl::isLiveAtEndOfBlock(this, bb) }
/**
* Gets a read of the source variable underlying this SSA definition that
@@ -236,7 +232,7 @@ module Ssa {
* - The reads of `this.Field` on lines 10 and 11 can be reached from the phi
* node between lines 9 and 10.
*/
final AssignableRead getAReadAtNode(ControlFlow::Node cfn) {
final AssignableRead getAReadAtNode(ControlFlowNode cfn) {
result = SsaImpl::getAReadAtNode(this, cfn)
}
@@ -310,72 +306,9 @@ module Ssa {
* Subsequent reads can be found by following the steps defined by
* `AssignableRead.getANextRead()`.
*/
final AssignableRead getAFirstReadAtNode(ControlFlow::Node cfn) {
final AssignableRead getAFirstReadAtNode(ControlFlowNode cfn) {
SsaImpl::firstReadSameVar(this, cfn) and
result.getAControlFlowNode() = cfn
}
/**
* Gets a last read of the source variable underlying this SSA definition.
* That is, a read that can reach the end of the enclosing callable, or
* another SSA definition for the source variable, without passing through
* any other read. Example:
*
* ```csharp
* int Field;
*
* void SetField(int i) {
* this.Field = i;
* Use(this.Field);
* if (i > 0)
* this.Field = i - 1;
* else if (i < 0)
* SetField(1);
* Use(this.Field);
* Use(this.Field);
* }
* ```
*
* - The reads of `i` on lines 7 and 8 are the last reads for the implicit
* parameter definition on line 3.
* - The read of `this.Field` on line 5 is a last read of the definition on
* line 4.
* - The read of `this.Field` on line 11 is a last read of the phi node
* between lines 9 and 10.
*/
deprecated final AssignableRead getALastRead() { result = this.getALastReadAtNode(_) }
/**
* Gets a last read of the source variable underlying this SSA definition at
* control flow node `cfn`. That is, a read that can reach the end of the
* enclosing callable, or another SSA definition for the source variable,
* without passing through any other read. Example:
*
* ```csharp
* int Field;
*
* void SetField(int i) {
* this.Field = i;
* Use(this.Field);
* if (i > 0)
* this.Field = i - 1;
* else if (i < 0)
* SetField(1);
* Use(this.Field);
* Use(this.Field);
* }
* ```
*
* - The reads of `i` on lines 7 and 8 are the last reads for the implicit
* parameter definition on line 3.
* - The read of `this.Field` on line 5 is a last read of the definition on
* line 4.
* - The read of `this.Field` on line 11 is a last read of the phi node
* between lines 9 and 10.
*/
deprecated final AssignableRead getALastReadAtNode(ControlFlow::Node cfn) {
SsaImpl::lastReadSameVar(this, cfn) and
result.getAControlFlowNode() = cfn
result.getControlFlowNode() = cfn
}
/**
@@ -426,7 +359,9 @@ module Ssa {
* This is either an expression, for example `x = 0`, a parameter, or a
* callable. Phi nodes have no associated syntax element.
*/
Element getElement() { result = this.getControlFlowNode().getAstNode() }
Element getElement() {
result.(ControlFlowElement).getControlFlowNode() = this.getControlFlowNode()
}
/** Gets the callable to which this SSA definition belongs. */
final Callable getEnclosingCallable() {
@@ -484,7 +419,7 @@ module Ssa {
* `M2` via the call on line 6.
*/
deprecated final predicate isCapturedVariableDefinitionFlowIn(
ImplicitEntryDefinition def, ControlFlow::Nodes::ElementNode c, boolean additionalCalls
ImplicitEntryDefinition def, ControlFlowNodes::ElementNode c, boolean additionalCalls
) {
none()
}
@@ -520,9 +455,7 @@ module Ssa {
override Element getElement() { result = ad.getElement() }
override string toString() {
result = SsaImpl::getToStringPrefix(this) + "SSA def(" + this.getSourceVariable() + ")"
}
override string toString() { result = "SSA def(" + this.getSourceVariable() + ")" }
override Location getLocation() { result = ad.getLocation() }
}
@@ -536,7 +469,7 @@ module Ssa {
*/
class ImplicitDefinition extends Definition, SsaImpl::WriteDefinition {
ImplicitDefinition() {
exists(ControlFlow::BasicBlock bb, SourceVariable v, int i | this.definesAt(v, bb, i) |
exists(BasicBlock bb, SourceVariable v, int i | this.definesAt(v, bb, i) |
SsaImpl::implicitEntryDefinition(bb, v) and
i = -1
or
@@ -554,25 +487,21 @@ module Ssa {
*/
class ImplicitEntryDefinition extends ImplicitDefinition {
ImplicitEntryDefinition() {
exists(ControlFlow::BasicBlock bb, SourceVariable v |
exists(BasicBlock bb, SourceVariable v |
this.definesAt(v, bb, -1) and
SsaImpl::implicitEntryDefinition(bb, v)
)
}
/** Gets the callable that this entry definition belongs to. */
final Callable getCallable() { result = this.getBasicBlock().getCallable() }
final Callable getCallable() { result = this.getBasicBlock().getEnclosingCallable() }
override Element getElement() { result = this.getCallable() }
override string toString() {
if this.getSourceVariable().getAssignable() instanceof LocalScopeVariable
then
result =
SsaImpl::getToStringPrefix(this) + "SSA capture def(" + this.getSourceVariable() + ")"
else
result =
SsaImpl::getToStringPrefix(this) + "SSA entry def(" + this.getSourceVariable() + ")"
then result = "SSA capture def(" + this.getSourceVariable() + ")"
else result = "SSA entry def(" + this.getSourceVariable() + ")"
}
override Location getLocation() { result = this.getCallable().getLocation() }
@@ -582,7 +511,7 @@ module Ssa {
class C = ImplicitParameterDefinition;
predicate relevantLocations(ImplicitParameterDefinition def, Location l1, Location l2) {
not def.getBasicBlock() instanceof ControlFlow::BasicBlocks::EntryBlock and
not def.getBasicBlock() instanceof EntryBasicBlock and
l1 = def.getParameter().getALocation() and
l2 = def.getBasicBlock().getLocation()
}
@@ -614,7 +543,7 @@ module Ssa {
override Element getElement() { result = this.getParameter() }
override string toString() {
result = SsaImpl::getToStringPrefix(this) + "SSA param(" + this.getParameter() + ")"
result = "SSA param(" + pragma[only_bind_out](this.getParameter()) + ")"
}
override Location getLocation() {
@@ -634,7 +563,7 @@ module Ssa {
private Call c;
ImplicitCallDefinition() {
exists(ControlFlow::BasicBlock bb, SourceVariable v, int i |
exists(BasicBlock bb, SourceVariable v, int i |
this.definesAt(v, bb, i) and
SsaImpl::updatesNamedFieldOrProp(bb, i, c, v, _)
)
@@ -656,9 +585,7 @@ module Ssa {
)
}
override string toString() {
result = SsaImpl::getToStringPrefix(this) + "SSA call def(" + this.getSourceVariable() + ")"
}
override string toString() { result = "SSA call def(" + this.getSourceVariable() + ")" }
override Location getLocation() { result = this.getCall().getLocation() }
}
@@ -671,9 +598,7 @@ module Ssa {
private Definition q;
ImplicitQualifierDefinition() {
exists(
ControlFlow::BasicBlock bb, int i, SourceVariables::QualifiedFieldOrPropSourceVariable v
|
exists(BasicBlock bb, int i, SourceVariables::QualifiedFieldOrPropSourceVariable v |
this.definesAt(v, bb, i)
|
SsaImpl::variableWriteQualifier(bb, i, v, _) and
@@ -684,10 +609,7 @@ module Ssa {
/** Gets the SSA definition for the qualifier. */
final Definition getQualifierDefinition() { result = q }
override string toString() {
result =
SsaImpl::getToStringPrefix(this) + "SSA qualifier def(" + this.getSourceVariable() + ")"
}
override string toString() { result = "SSA qualifier def(" + this.getSourceVariable() + ")" }
override Location getLocation() { result = this.getQualifierDefinition().getLocation() }
}
@@ -723,13 +645,11 @@ module Ssa {
final Definition getAnInput() { this.hasInputFromBlock(result, _) }
/** Holds if `inp` is an input to this phi node along the edge originating in `bb`. */
predicate hasInputFromBlock(Definition inp, ControlFlow::BasicBlock bb) {
predicate hasInputFromBlock(Definition inp, BasicBlock bb) {
inp = SsaImpl::phiHasInputFromBlock(this, bb)
}
override string toString() {
result = SsaImpl::getToStringPrefix(this) + "SSA phi(" + this.getSourceVariable() + ")"
}
override string toString() { result = "SSA phi(" + this.getSourceVariable() + ")" }
/*
* The location of a phi node is the same as the location of the first node

Some files were not shown because too many files have changed in this diff Show More