mirror of
https://github.com/github/codeql.git
synced 2026-06-05 13:37:06 +02:00
Compare commits
93 Commits
yoff/pytho
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1f91f915c7 | ||
|
|
ba8eebe2b5 | ||
|
|
dc1409e5f4 | ||
|
|
284f42bb9e | ||
|
|
2f3524de74 | ||
|
|
b32573b060 | ||
|
|
cd2398aeea | ||
|
|
d6892eaf0d | ||
|
|
d2972cb53f | ||
|
|
5576d30780 | ||
|
|
da999ee440 | ||
|
|
3da195f50f | ||
|
|
93a4b427e3 | ||
|
|
0430c71318 | ||
|
|
52f2a5825a | ||
|
|
d55ff83568 | ||
|
|
f34275636c | ||
|
|
0a801440b9 | ||
|
|
7edf0100cc | ||
|
|
167c837088 | ||
|
|
6f2cc43f32 | ||
|
|
5042fdee84 | ||
|
|
04341c47bd | ||
|
|
af45e53e77 | ||
|
|
b27d08ee32 | ||
|
|
d11fc3a00e | ||
|
|
20ce679d61 | ||
|
|
f62ebef9e0 | ||
|
|
c3ef1ddd64 | ||
|
|
dede5bc49b | ||
|
|
ad97b6dd64 | ||
|
|
9d5dfea5c5 | ||
|
|
dc0c7d7ec2 | ||
|
|
61a5cece56 | ||
|
|
566a92e555 | ||
|
|
be9c785cb2 | ||
|
|
1fd31d0ddd | ||
|
|
c4e3720d8a | ||
|
|
0547e9c98d | ||
|
|
2a3cff382c | ||
|
|
c610af88d3 | ||
|
|
fa63dad1d1 | ||
|
|
019a5c01ad | ||
|
|
c1c9287535 | ||
|
|
d1226b71de | ||
|
|
71a363545a | ||
|
|
3f3bed62d3 | ||
|
|
21f216af8c | ||
|
|
1751d70c62 | ||
|
|
ac8eb50c26 | ||
|
|
1ecdc3614f | ||
|
|
e3b3888bee | ||
|
|
ef9306d82c | ||
|
|
56822f8ee1 | ||
|
|
62207f152c | ||
|
|
d5f94475b5 | ||
|
|
00e95a0757 | ||
|
|
c695c151ea | ||
|
|
b38440490a | ||
|
|
5e5a0437e1 | ||
|
|
aee33a0cc9 | ||
|
|
df15a719cb | ||
|
|
812e8e6b34 | ||
|
|
80c6f082d1 | ||
|
|
d95d99848c | ||
|
|
8937e22735 | ||
|
|
37589dd8a0 | ||
|
|
a159dc1c66 | ||
|
|
cc12740c0e | ||
|
|
acb5c0e70f | ||
|
|
6042adebae | ||
|
|
ec13e1bcd3 | ||
|
|
e8779295ee | ||
|
|
fa758d6bf5 | ||
|
|
fa9426c749 | ||
|
|
0ecca91dea | ||
|
|
f669a4f3bf | ||
|
|
3275c814bd | ||
|
|
9a180036a5 | ||
|
|
93e7ab52b7 | ||
|
|
facb3b681d | ||
|
|
b67694b2ab | ||
|
|
dc864762c3 | ||
|
|
dd35bc0722 | ||
|
|
043ec857ab | ||
|
|
f5b17b0b48 | ||
|
|
26dca558c7 | ||
|
|
57ce0b3d51 | ||
|
|
408ba2e139 | ||
|
|
7632bdba88 | ||
|
|
4b830c1864 | ||
|
|
d6c8767647 | ||
|
|
ec815397a2 |
@@ -11,10 +11,6 @@
|
||||
"java/ql/lib/semmle/code/java/dataflow/internal/rangeanalysis/SignAnalysisCommon.qll",
|
||||
"csharp/ql/lib/semmle/code/csharp/dataflow/internal/rangeanalysis/SignAnalysisCommon.qll"
|
||||
],
|
||||
"Bound Java/C#": [
|
||||
"java/ql/lib/semmle/code/java/dataflow/Bound.qll",
|
||||
"csharp/ql/lib/semmle/code/csharp/dataflow/Bound.qll"
|
||||
],
|
||||
"ModulusAnalysis Java/C#": [
|
||||
"java/ql/lib/semmle/code/java/dataflow/ModulusAnalysis.qll",
|
||||
"csharp/ql/lib/semmle/code/csharp/dataflow/ModulusAnalysis.qll"
|
||||
|
||||
@@ -9,6 +9,7 @@ dependencies:
|
||||
codeql/controlflow: ${workspace}
|
||||
codeql/dataflow: ${workspace}
|
||||
codeql/mad: ${workspace}
|
||||
codeql/rangeanalysis: ${workspace}
|
||||
codeql/ssa: ${workspace}
|
||||
codeql/threat-models: ${workspace}
|
||||
codeql/tutorial: ${workspace}
|
||||
|
||||
@@ -4,67 +4,31 @@
|
||||
overlay[local?]
|
||||
module;
|
||||
|
||||
private import internal.rangeanalysis.BoundSpecific
|
||||
private import csharp as CS
|
||||
private import semmle.code.csharp.dataflow.SSA::Ssa
|
||||
private import semmle.code.csharp.dataflow.internal.rangeanalysis.ConstantUtils as CU
|
||||
private import semmle.code.csharp.dataflow.internal.rangeanalysis.RangeUtils as RU
|
||||
private import semmle.code.csharp.dataflow.internal.rangeanalysis.SsaUtils as SU
|
||||
private import codeql.rangeanalysis.Bound as SharedBound
|
||||
|
||||
private newtype TBound =
|
||||
TBoundZero() or
|
||||
TBoundSsa(SsaVariable v) { v.getSourceVariable().getType() instanceof IntegralType } or
|
||||
TBoundExpr(Expr e) {
|
||||
interestingExprBound(e) and
|
||||
not exists(SsaVariable v | e = v.getAUse())
|
||||
}
|
||||
/** Provides C#-specific definitions for bounds. */
|
||||
private module BoundDefs implements SharedBound::BoundDefinitions<CS::Location> {
|
||||
class Type = CS::Type;
|
||||
|
||||
/**
|
||||
* A bound that may be inferred for an expression plus/minus an integer delta.
|
||||
*/
|
||||
abstract class Bound extends TBound {
|
||||
/** Gets a textual representation of this bound. */
|
||||
abstract string toString();
|
||||
class SsaVariable = SU::SsaVariable;
|
||||
|
||||
/** Gets an expression that equals this bound plus `delta`. */
|
||||
abstract Expr getExpr(int delta);
|
||||
class SsaSourceVariable = SourceVariable;
|
||||
|
||||
/** Gets an expression that equals this bound. */
|
||||
Expr getExpr() { result = this.getExpr(0) }
|
||||
class Expr = CS::ControlFlowNodes::ExprNode;
|
||||
|
||||
/** Gets the location of this bound. */
|
||||
abstract Location getLocation();
|
||||
class IntegralType = CS::IntegralType;
|
||||
|
||||
class ConstantIntegerExpr = CU::ConstantIntegerExpr;
|
||||
|
||||
/** Holds if `e` is a bound expression and it is not an SSA variable read. */
|
||||
predicate interestingExprBound(Expr e) { CU::systemArrayLengthAccess(e.getExpr()) }
|
||||
}
|
||||
|
||||
/**
|
||||
* The bound that corresponds to the integer 0. This is used to represent all
|
||||
* integer bounds as bounds are always accompanied by an added integer delta.
|
||||
*/
|
||||
class ZeroBound extends Bound, TBoundZero {
|
||||
override string toString() { result = "0" }
|
||||
module BoundImpl = SharedBound::Bound<CS::Location, BoundDefs>;
|
||||
|
||||
override Expr getExpr(int delta) { result.(ConstantIntegerExpr).getIntValue() = delta }
|
||||
|
||||
override Location getLocation() { result.hasLocationInfo("", 0, 0, 0, 0) }
|
||||
}
|
||||
|
||||
/**
|
||||
* A bound corresponding to the value of an SSA variable.
|
||||
*/
|
||||
class SsaBound extends Bound, TBoundSsa {
|
||||
/** Gets the SSA variable that equals this bound. */
|
||||
SsaVariable getSsa() { this = TBoundSsa(result) }
|
||||
|
||||
override string toString() { result = this.getSsa().toString() }
|
||||
|
||||
override Expr getExpr(int delta) { result = this.getSsa().getAUse() and delta = 0 }
|
||||
|
||||
override Location getLocation() { result = this.getSsa().getLocation() }
|
||||
}
|
||||
|
||||
/**
|
||||
* A bound that corresponds to the value of a specific expression that might be
|
||||
* interesting, but isn't otherwise represented by the value of an SSA variable.
|
||||
*/
|
||||
class ExprBound extends Bound, TBoundExpr {
|
||||
override string toString() { result = this.getExpr().toString() }
|
||||
|
||||
override Expr getExpr(int delta) { this = TBoundExpr(result) and delta = 0 }
|
||||
|
||||
override Location getLocation() { result = this.getExpr().getLocation() }
|
||||
}
|
||||
import BoundImpl
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
/**
|
||||
* Provides C#-specific definitions for bounds.
|
||||
*/
|
||||
|
||||
private import csharp as CS
|
||||
private import semmle.code.csharp.dataflow.SSA::Ssa as Ssa
|
||||
private import semmle.code.csharp.dataflow.internal.rangeanalysis.ConstantUtils as CU
|
||||
private import semmle.code.csharp.dataflow.internal.rangeanalysis.RangeUtils as RU
|
||||
private import semmle.code.csharp.dataflow.internal.rangeanalysis.SsaUtils as SU
|
||||
|
||||
class SsaVariable = SU::SsaVariable;
|
||||
|
||||
class Expr = CS::ControlFlowNodes::ExprNode;
|
||||
|
||||
class Location = CS::Location;
|
||||
|
||||
class IntegralType = CS::IntegralType;
|
||||
|
||||
class ConstantIntegerExpr = CU::ConstantIntegerExpr;
|
||||
|
||||
/** Holds if `e` is a bound expression and it is not an SSA variable read. */
|
||||
predicate interestingExprBound(Expr e) { CU::systemArrayLengthAccess(e.getExpr()) }
|
||||
@@ -0,0 +1,139 @@
|
||||
.. _codeql-cli-2.25.6:
|
||||
|
||||
==========================
|
||||
CodeQL 2.25.6 (2026-06-04)
|
||||
==========================
|
||||
|
||||
.. contents:: Contents
|
||||
:depth: 2
|
||||
:local:
|
||||
:backlinks: none
|
||||
|
||||
This is an overview of changes in the CodeQL CLI and relevant CodeQL query and library packs. For additional updates on changes to the CodeQL code scanning experience, check out the `code scanning section on the GitHub blog <https://github.blog/tag/code-scanning/>`__, `relevant GitHub Changelog updates <https://github.blog/changelog/label/application-security/>`__, `changes in the CodeQL extension for Visual Studio Code <https://marketplace.visualstudio.com/items/GitHub.vscode-codeql/changelog>`__, and the `CodeQL Action changelog <https://github.com/github/codeql-action/blob/main/CHANGELOG.md>`__.
|
||||
|
||||
Security Coverage
|
||||
-----------------
|
||||
|
||||
CodeQL 2.25.6 runs a total of 496 security queries when configured with the Default suite (covering 169 CWE). The Extended suite enables an additional 131 queries (covering 32 more CWE).
|
||||
|
||||
CodeQL CLI
|
||||
----------
|
||||
|
||||
Improvements
|
||||
~~~~~~~~~~~~
|
||||
|
||||
* When the :code:`git` executable is available, CodeQL can now obtain configuration and queries from SHA-256 Git repositories, and infer Git metadata about them.
|
||||
|
||||
Miscellaneous
|
||||
~~~~~~~~~~~~~
|
||||
|
||||
* The build of Eclipse Temurin OpenJDK that is used to run the CodeQL CLI has been updated to version 21.0.11.
|
||||
|
||||
Query Packs
|
||||
-----------
|
||||
|
||||
Bug Fixes
|
||||
~~~~~~~~~
|
||||
|
||||
GitHub Actions
|
||||
""""""""""""""
|
||||
|
||||
* Adjusted (minor) help file descriptions for queries: :code:`actions/untrusted-checkout/critical`, :code:`actions/untrusted-checkout/high`, :code:`actions/untrusted-checkout/medium`. Clarified wording on a minor point, added one more listed resource and added one more recommendation for things to check.
|
||||
|
||||
Major Analysis Improvements
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
GitHub Actions
|
||||
""""""""""""""
|
||||
|
||||
* Adjusted :code:`actions/untrusted-checkout/critical` to align more with other untrusted resource queries, where the alert location is the location where the artifact is obtained from (the checkout point). This aligns with the other 2 related queries. This will cause the same alerts to re-open for closed alerts of this query.
|
||||
|
||||
Minor Analysis Improvements
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
GitHub Actions
|
||||
""""""""""""""
|
||||
|
||||
* Altered the alert message for clarity for queries: :code:`actions/untrusted-checkout/critical`, :code:`actions/untrusted-checkout/high`.
|
||||
* The :code:`actions/unpinned-tag` query now recognizes 64-character SHA-256 commit hashes as properly pinned references, in addition to 40-character SHA-1 hashes.
|
||||
|
||||
Query Metadata Changes
|
||||
~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
GitHub Actions
|
||||
""""""""""""""
|
||||
|
||||
* Reversed adjustment of the name of :code:`actions/untrusted-checkout/high`, but kept the portion of the previous change for the word "trusted" to "privileged". Added a missing "a" to phrasing in :code:`actions/untrusted-checkout/high` and :code:`actions/untrusted-checkout/medium`.
|
||||
|
||||
Language Libraries
|
||||
------------------
|
||||
|
||||
Major Analysis Improvements
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Swift
|
||||
"""""
|
||||
|
||||
* Upgraded to allow analysis of Swift 6.3.2.
|
||||
|
||||
Minor Analysis Improvements
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
C/C++
|
||||
"""""
|
||||
|
||||
* Added flow source models for :code:`scanf_s` and related functions.
|
||||
* Added a :code:`Call` column to :code:`LocalFlowSourceFunction::hasLocalFlowSource` and :code:`RemoteFlowSourceFunction::hasRemoteFlowSource`. The old predicates without a :code:`Call` column continue to be supported.
|
||||
|
||||
C#
|
||||
""
|
||||
|
||||
* Full support for C# 14 / .NET 10. All new language features are now supported by the extractor. The QL library and data flow analysis now support the new C# 14 language constructs and include generated Models as Data (MaD) models for the .NET 10 runtime.
|
||||
* C# 14: Added support for user-defined instance increment/decrement operators.
|
||||
|
||||
Java/Kotlin
|
||||
"""""""""""
|
||||
|
||||
* Added LLM-generated source and sink models for :code:`org.apache.avro`.
|
||||
|
||||
JavaScript/TypeScript
|
||||
"""""""""""""""""""""
|
||||
|
||||
* The sensitive data heuristics used to identify code that handles passwords and private data have been improved. Most of the changes permit more variations of established patterns, thereby finding more sensitive data. Queries that use the sensitive data library (for example :code:`js/clear-text-logging`) may find more correct results and fewer false positive results after these changes.
|
||||
|
||||
Python
|
||||
""""""
|
||||
|
||||
* The sensitive data heuristics used to identify code that handles passwords and private data have been improved. Most of the changes permit more variations of established patterns, thereby finding more sensitive data. Queries that use the sensitive data library (for example :code:`py/clear-text-logging-sensitive-data`) may find more correct results and fewer false positive results after these changes.
|
||||
|
||||
Swift
|
||||
"""""
|
||||
|
||||
* The sensitive data heuristics used to identify code that handles passwords and private data have been improved. Most of the changes permit more variations of established patterns, thereby finding more sensitive data. Queries that use the sensitive data library (for example :code:`swift/cleartext-logging`) may find more correct results and fewer false positive results after these changes.
|
||||
|
||||
GitHub Actions
|
||||
""""""""""""""
|
||||
|
||||
* The GitHub Actions analysis now recognizes more Bash regex checks that restrict a value to alphanumeric characters, including regexes like :code:`^[0-9a-zA-Z]{40}([0-9a-zA-Z]{24})?$` which check for a SHA-1 or SHA-256 hash. This may reduce false positive results where command output is validated with grouped or optional alphanumeric patterns before being used.
|
||||
|
||||
Rust
|
||||
""""
|
||||
|
||||
* The sensitive data heuristics used to identify code that handles passwords and private data have been improved. Most of the changes permit more variations of established patterns, thereby finding more sensitive data. Queries that use the sensitive data library (for example :code:`rust/cleartext-logging`) may find more correct results and fewer false positive results after these changes.
|
||||
|
||||
Deprecated APIs
|
||||
~~~~~~~~~~~~~~~
|
||||
|
||||
C/C++
|
||||
"""""
|
||||
|
||||
* The :code:`UsingAliasTypedefType` class has been deprecated. Use :code:`TypeAliasType` instead.
|
||||
|
||||
New Features
|
||||
~~~~~~~~~~~~
|
||||
|
||||
C/C++
|
||||
"""""
|
||||
|
||||
* Added a :code:`getOriginalTemplate` predicate to :code:`TemplateClass`, :code:`TemplateFunction`, :code:`TemplateVariable`, and :code:`AliasTemplateType`, which yields the class member template the template was generated from. The predicates only have results for templates that are members of class template instantiations.
|
||||
* Added :code:`AliasTemplateType` and :code:`AliasTemplateInstantiationType` classes, representing C++ alias templates and their instantiations.
|
||||
@@ -11,6 +11,7 @@ A list of queries for each suite and language `is available here <https://docs.g
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
codeql-cli-2.25.6
|
||||
codeql-cli-2.25.5
|
||||
codeql-cli-2.25.4
|
||||
codeql-cli-2.25.3
|
||||
|
||||
@@ -9,9 +9,9 @@ import semmle.go.dataflow.internal.FlowSummaryImpl as FlowSummaryImpl
|
||||
import utils.test.InlineFlowTest
|
||||
|
||||
module Config implements DataFlow::ConfigSig {
|
||||
predicate isSource(DataFlow::Node src) { sourceNode(src, "qltest") }
|
||||
predicate isSource(DataFlow::Node source) { sourceNode(source, "qltest") }
|
||||
|
||||
predicate isSink(DataFlow::Node src) { sinkNode(src, "qltest") }
|
||||
predicate isSink(DataFlow::Node sink) { sinkNode(sink, "qltest") }
|
||||
}
|
||||
|
||||
import ValueFlowTest<Config>
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
reverseRead
|
||||
| main.go:23:3:23:5 | out | Origin of readStep is missing a PostUpdateNode. |
|
||||
@@ -4,7 +4,7 @@ func source() string {
|
||||
return "untrusted data"
|
||||
}
|
||||
|
||||
func sink(string) {
|
||||
func sink(any) {
|
||||
}
|
||||
|
||||
type A struct {
|
||||
@@ -19,6 +19,10 @@ func functionWithVarArgsParameter(s ...string) string {
|
||||
return s[1]
|
||||
}
|
||||
|
||||
func functionWithVarArgsOutParameter(in string, out ...*string) {
|
||||
*out[0] = in
|
||||
}
|
||||
|
||||
func functionWithSliceOfStructsParameter(s []A) string {
|
||||
return s[1].f
|
||||
}
|
||||
@@ -38,6 +42,12 @@ func main() {
|
||||
sink(functionWithVarArgsParameter(sSlice...)) // $ hasValueFlow="call to functionWithVarArgsParameter"
|
||||
sink(functionWithVarArgsParameter(s0, s1)) // $ hasValueFlow="call to functionWithVarArgsParameter"
|
||||
|
||||
var out1 *string
|
||||
var out2 *string
|
||||
functionWithVarArgsOutParameter(source(), out1, out2)
|
||||
sink(out1) // $ MISSING: hasValueFlow="out1"
|
||||
sink(out2) // $ MISSING: hasValueFlow="out2"
|
||||
|
||||
sliceOfStructs := []A{{f: source()}}
|
||||
sink(sliceOfStructs[0].f) // $ hasValueFlow="selection of f"
|
||||
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
invalidModelRow
|
||||
testFailures
|
||||
@@ -0,0 +1,21 @@
|
||||
extensions:
|
||||
- addsTo:
|
||||
pack: codeql/go-all
|
||||
extensible: summaryModel
|
||||
data:
|
||||
- ["github.com/nonexistent/test", "", False, "FunctionWithParameter", "", "", "Argument[0]", "ReturnValue", "value", "manual"]
|
||||
- ["github.com/nonexistent/test", "", False, "FunctionWithSliceParameter", "", "", "Argument[0].ArrayElement", "ReturnValue", "value", "manual"]
|
||||
- ["github.com/nonexistent/test", "", False, "FunctionWithVarArgsParameter", "", "", "Argument[0].ArrayElement", "ReturnValue", "value", "manual"]
|
||||
- ["github.com/nonexistent/test", "", False, "FunctionWithVarArgsOutParameter", "", "", "Argument[0]", "Argument[1].ArrayElement", "value", "manual"]
|
||||
- ["github.com/nonexistent/test", "", False, "FunctionWithSliceOfStructsParameter", "", "", "Argument[0].ArrayElement.Field[github.com/nonexistent/test.A.Field]", "ReturnValue", "value", "manual"]
|
||||
- ["github.com/nonexistent/test", "", False, "FunctionWithVarArgsOfStructsParameter", "", "", "Argument[0].ArrayElement.Field[github.com/nonexistent/test.A.Field]", "ReturnValue", "value", "manual"]
|
||||
- addsTo:
|
||||
pack: codeql/go-all
|
||||
extensible: sourceModel
|
||||
data:
|
||||
- ["github.com/nonexistent/test", "", False, "VariadicSource", "", "", "Argument[0]", "qltest", "manual"]
|
||||
- addsTo:
|
||||
pack: codeql/go-all
|
||||
extensible: sinkModel
|
||||
data:
|
||||
- ["github.com/nonexistent/test", "", False, "VariadicSink", "", "", "Argument[0]", "qltest", "manual"]
|
||||
@@ -0,0 +1,22 @@
|
||||
import go
|
||||
import semmle.go.dataflow.ExternalFlow
|
||||
import ModelValidation
|
||||
import utils.test.InlineFlowTest
|
||||
|
||||
module Config implements DataFlow::ConfigSig {
|
||||
predicate isSource(DataFlow::Node source) {
|
||||
sourceNode(source, "qltest")
|
||||
or
|
||||
exists(Function fn | fn.hasQualifiedName(_, ["source", "taint"]) |
|
||||
source = fn.getACall().getResult()
|
||||
)
|
||||
}
|
||||
|
||||
predicate isSink(DataFlow::Node sink) {
|
||||
sinkNode(sink, "qltest")
|
||||
or
|
||||
exists(Function fn | fn.hasQualifiedName(_, "sink") | sink = fn.getACall().getAnArgument())
|
||||
}
|
||||
}
|
||||
|
||||
import FlowTest<Config, Config>
|
||||
@@ -0,0 +1,5 @@
|
||||
module semmle.go.Packages
|
||||
|
||||
go 1.25
|
||||
|
||||
require github.com/nonexistent/test v0.0.0-20200203000000-0000000000000
|
||||
@@ -0,0 +1,56 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/nonexistent/test"
|
||||
)
|
||||
|
||||
func source() string {
|
||||
return "untrusted data"
|
||||
}
|
||||
|
||||
func sink(any) {
|
||||
}
|
||||
|
||||
func main() {
|
||||
s := source()
|
||||
sink(test.FunctionWithParameter(s)) // $ hasValueFlow="call to FunctionWithParameter"
|
||||
|
||||
stringSlice := []string{source()}
|
||||
sink(stringSlice[0]) // $ hasValueFlow="index expression"
|
||||
|
||||
s0 := ""
|
||||
s1 := source()
|
||||
sSlice := []string{s0, s1}
|
||||
sink(test.FunctionWithParameter(sSlice[1])) // $ hasValueFlow="call to FunctionWithParameter"
|
||||
sink(test.FunctionWithSliceParameter(sSlice)) // $ hasValueFlow="call to FunctionWithSliceParameter"
|
||||
sink(test.FunctionWithVarArgsParameter(sSlice...)) // $ hasValueFlow="call to FunctionWithVarArgsParameter"
|
||||
sink(test.FunctionWithVarArgsParameter(s0, s1)) // $ hasValueFlow="call to FunctionWithVarArgsParameter"
|
||||
|
||||
var out1 *string
|
||||
var out2 *string
|
||||
test.FunctionWithVarArgsOutParameter(source(), out1, out2)
|
||||
sink(out1) // $ MISSING: hasValueFlow="out1"
|
||||
sink(out2) // $ MISSING: hasValueFlow="out2"
|
||||
|
||||
sliceOfStructs := []test.A{{Field: source()}}
|
||||
sink(sliceOfStructs[0].Field) // $ hasValueFlow="selection of Field"
|
||||
|
||||
a0 := test.A{Field: ""}
|
||||
a1 := test.A{Field: source()}
|
||||
aSlice := []test.A{a0, a1}
|
||||
sink(test.FunctionWithSliceOfStructsParameter(aSlice)) // $ hasValueFlow="call to FunctionWithSliceOfStructsParameter"
|
||||
sink(test.FunctionWithVarArgsOfStructsParameter(aSlice...)) // $ hasValueFlow="call to FunctionWithVarArgsOfStructsParameter"
|
||||
sink(test.FunctionWithVarArgsOfStructsParameter(a0, a1)) // $ hasValueFlow="call to FunctionWithVarArgsOfStructsParameter"
|
||||
|
||||
var variadicSource string
|
||||
test.VariadicSource(&variadicSource)
|
||||
sink(variadicSource) // $ MISSING: hasTaintFlow="variadicSource"
|
||||
sink(&variadicSource) // $ MISSING: hasTaintFlow="&..."
|
||||
|
||||
var variadicSourcePtr *string
|
||||
test.VariadicSource(variadicSourcePtr)
|
||||
sink(variadicSourcePtr) // $ MISSING: hasTaintFlow="variadicSourcePtr"
|
||||
sink(*variadicSourcePtr) // $ MISSING: hasTaintFlow="star expression"
|
||||
|
||||
test.VariadicSink(source()) // $ hasTaintFlow="[]type{args}"
|
||||
}
|
||||
32
go/ql/test/library-tests/semmle/go/dataflow/VarArgsWithExternalFlow/vendor/github.com/nonexistent/test/stub.go
generated
vendored
Normal file
32
go/ql/test/library-tests/semmle/go/dataflow/VarArgsWithExternalFlow/vendor/github.com/nonexistent/test/stub.go
generated
vendored
Normal file
@@ -0,0 +1,32 @@
|
||||
package test
|
||||
|
||||
type A struct {
|
||||
Field string
|
||||
}
|
||||
|
||||
func FunctionWithParameter(s string) string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func FunctionWithSliceParameter(s []string) string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func FunctionWithVarArgsParameter(s ...string) string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func FunctionWithVarArgsOutParameter(in string, out ...*string) {
|
||||
}
|
||||
|
||||
func FunctionWithSliceOfStructsParameter(s []A) string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func FunctionWithVarArgsOfStructsParameter(s ...A) string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func VariadicSource(s ...*string) {}
|
||||
|
||||
func VariadicSink(s ...string) {}
|
||||
3
go/ql/test/library-tests/semmle/go/dataflow/VarArgsWithExternalFlow/vendor/modules.txt
vendored
Normal file
3
go/ql/test/library-tests/semmle/go/dataflow/VarArgsWithExternalFlow/vendor/modules.txt
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
# github.com/nonexistent/test v0.0.0-20200203000000-0000000000000
|
||||
## explicit
|
||||
github.com/nonexistent/test
|
||||
@@ -20,6 +20,9 @@ class SummaryModelTest extends DataFlow::FunctionModel {
|
||||
this.hasQualifiedName("github.com/nonexistent/test", "FunctionWithVarArgsParameter") and
|
||||
(inp.isParameter(_) and outp.isResult())
|
||||
or
|
||||
this.hasQualifiedName("github.com/nonexistent/test", "FunctionWithVarArgsOutParameter") and
|
||||
(inp.isParameter(0) and outp.isParameter(any(int i | i >= 1)))
|
||||
or
|
||||
this.hasQualifiedName("github.com/nonexistent/test", "FunctionWithSliceOfStructsParameter") and
|
||||
(inp.isParameter(0) and outp.isResult())
|
||||
or
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
module semmle.go.Packages
|
||||
|
||||
go 1.17
|
||||
go 1.25
|
||||
|
||||
require github.com/nonexistent/test v0.0.0-20200203000000-0000000000000
|
||||
|
||||
@@ -8,7 +8,7 @@ func source() string {
|
||||
return "untrusted data"
|
||||
}
|
||||
|
||||
func sink(string) {
|
||||
func sink(any) {
|
||||
}
|
||||
|
||||
func main() {
|
||||
@@ -21,10 +21,17 @@ func main() {
|
||||
s0 := ""
|
||||
s1 := source()
|
||||
sSlice := []string{s0, s1}
|
||||
sink(test.FunctionWithParameter(sSlice[1])) // $ hasValueFlow="call to FunctionWithParameter"
|
||||
sink(test.FunctionWithSliceParameter(sSlice)) // $ hasTaintFlow="call to FunctionWithSliceParameter" MISSING: hasValueFlow="call to FunctionWithSliceParameter"
|
||||
sink(test.FunctionWithVarArgsParameter(sSlice...)) // $ hasTaintFlow="call to FunctionWithVarArgsParameter" MISSING: hasValueFlow="call to FunctionWithVarArgsParameter"
|
||||
sink(test.FunctionWithVarArgsParameter(s0, s1)) // $ MISSING: hasValueFlow="call to FunctionWithVarArgsParameter"
|
||||
sink(test.FunctionWithParameter(sSlice[1])) // $ hasValueFlow="call to FunctionWithParameter"
|
||||
sink(test.FunctionWithSliceParameter(sSlice)) // $ hasTaintFlow="call to FunctionWithSliceParameter" MISSING: hasValueFlow="call to FunctionWithSliceParameter"
|
||||
sink(test.FunctionWithVarArgsParameter(sSlice...)) // $ hasTaintFlow="call to FunctionWithVarArgsParameter" MISSING: hasValueFlow="call to FunctionWithVarArgsParameter"
|
||||
randomFunctionWithMoreThanOneParameter(1, 2, 3, 4, 5) // This is needed to make the next line pass, because we need to have seen a call to a function with at least 2 parameters for ParameterInput to exist with index 1.
|
||||
sink(test.FunctionWithVarArgsParameter(s0, s1)) // $ hasValueFlow="call to FunctionWithVarArgsParameter"
|
||||
|
||||
var out1 *string
|
||||
var out2 *string
|
||||
test.FunctionWithVarArgsOutParameter(source(), out1, out2)
|
||||
sink(out1) // $ hasValueFlow="out1"
|
||||
sink(out2) // $ hasValueFlow="out2"
|
||||
|
||||
sliceOfStructs := []test.A{{Field: source()}}
|
||||
sink(sliceOfStructs[0].Field) // $ hasValueFlow="selection of Field"
|
||||
@@ -37,3 +44,6 @@ func main() {
|
||||
sink(test.FunctionWithVarArgsOfStructsParameter(aSlice...)) // $ MISSING: hasValueFlow="call to FunctionWithVarArgsOfStructsParameter"
|
||||
sink(test.FunctionWithVarArgsOfStructsParameter(a0, a1)) // $ MISSING: hasValueFlow="call to FunctionWithVarArgsOfStructsParameter"
|
||||
}
|
||||
|
||||
func randomFunctionWithMoreThanOneParameter(i1, i2, i3, i4, i5 int) {
|
||||
}
|
||||
|
||||
Binary file not shown.
@@ -16,6 +16,9 @@ func FunctionWithVarArgsParameter(s ...string) string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func FunctionWithVarArgsOutParameter(in string, out ...*string) {
|
||||
}
|
||||
|
||||
func FunctionWithSliceOfStructsParameter(s []A) string {
|
||||
return ""
|
||||
}
|
||||
|
||||
@@ -194,7 +194,7 @@ org.apache.hc.core5.http,73,2,45,,,,,,,,,,,,,1,,,,,,,,,,,,,,,,,,,,,72,,,,,,,,,,,
|
||||
org.apache.hc.core5.net,,,18,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,18,
|
||||
org.apache.hc.core5.util,,,24,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,18,6
|
||||
org.apache.hive.hcatalog.templeton,1,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1,,,,,,,,,,,,,,,,
|
||||
org.apache.http,48,3,95,,,,,,,,,,,,,2,,,,,,,,,,,,,,,,,,,,,46,,,,,,,,,,,,,,,,3,86,9
|
||||
org.apache.http,53,3,117,,,,,,,,,,,,,2,,,,,,,,,,,,,,,,,,,,,51,,,,,,,,,,,,,,,,3,108,9
|
||||
org.apache.ibatis.jdbc,6,,57,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,6,,,,,,,,,,,,,,,57,
|
||||
org.apache.ibatis.mapping,,,1,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1,
|
||||
org.apache.log4j,11,,,,,,,,,,,,,,,,,,,,,,11,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
|
||||
|
||||
|
@@ -13,7 +13,7 @@ Java framework & library support
|
||||
`Apache Commons IO <https://commons.apache.org/proper/commons-io/>`_,``org.apache.commons.io``,,570,124,105,,,,,15
|
||||
`Apache Commons Lang <https://commons.apache.org/proper/commons-lang/>`_,``org.apache.commons.lang3``,,425,7,,,,,,
|
||||
`Apache Commons Text <https://commons.apache.org/proper/commons-text/>`_,``org.apache.commons.text``,,272,,,,,,,
|
||||
`Apache HttpComponents <https://hc.apache.org/>`_,"``org.apache.hc.core5.*``, ``org.apache.http``",5,183,122,,3,,,,119
|
||||
`Apache HttpComponents <https://hc.apache.org/>`_,"``org.apache.hc.core5.*``, ``org.apache.http``",5,205,127,,3,,,,124
|
||||
`Apache Log4j 2 <https://logging.apache.org/log4j/2.0/>`_,``org.apache.logging.log4j``,,8,359,,,,,,
|
||||
`Apache Struts <https://struts.apache.org/>`_,"``org.apache.struts2``, ``org.apache.struts.beanvalidation.validation.interceptor``",,3877,14,,,,,,
|
||||
`Apache Velocity <https://velocity.apache.org/>`_,"``org.apache.velocity.app``, ``org.apache.velocity.runtime``",,,8,,,,,,
|
||||
@@ -41,5 +41,5 @@ Java framework & library support
|
||||
`Thymeleaf <https://www.thymeleaf.org/>`_,``org.thymeleaf``,,2,2,,,,,,
|
||||
`jOOQ <https://www.jooq.org/>`_,``org.jooq``,,,1,,,1,,,
|
||||
Others,"``actions.osgi``, ``antlr``, ``ch.ethz.ssh2``, ``cn.hutool.core.codec``, ``com.alibaba.com.caucho.hessian.io``, ``com.alibaba.druid.sql``, ``com.alibaba.fastjson2``, ``com.amazonaws.auth``, ``com.auth0.jwt.algorithms``, ``com.azure.identity``, ``com.caucho.burlap.io``, ``com.caucho.hessian.io``, ``com.cedarsoftware.util.io``, ``com.esotericsoftware.kryo.io``, ``com.esotericsoftware.kryo5.io``, ``com.esotericsoftware.yamlbeans``, ``com.hubspot.jinjava``, ``com.jcraft.jsch``, ``com.microsoft.sqlserver.jdbc``, ``com.mitchellbosecke.pebble``, ``com.opensymphony.xwork2``, ``com.sshtools.j2ssh.authentication``, ``com.sun.crypto.provider``, ``com.sun.jndi.ldap``, ``com.sun.net.httpserver``, ``com.sun.net.ssl``, ``com.sun.rowset``, ``com.sun.security.auth.module``, ``com.sun.security.ntlm``, ``com.sun.security.sasl.digest``, ``com.thoughtworks.xstream``, ``com.trilead.ssh2``, ``com.unboundid.ldap.sdk``, ``com.zaxxer.hikari``, ``flexjson``, ``hudson``, ``io.jsonwebtoken``, ``io.undertow.server.handlers.resource``, ``javafx.scene.web``, ``jenkins``, ``jodd.json``, ``liquibase.database.jvm``, ``liquibase.statement.core``, ``net.lingala.zip4j``, ``net.schmizz.sshj``, ``net.sf.json``, ``net.sf.saxon.s9api``, ``ognl``, ``org.acegisecurity``, ``org.antlr.runtime``, ``org.apache.avro``, ``org.apache.commons.codec``, ``org.apache.commons.compress.archivers.tar``, ``org.apache.commons.exec``, ``org.apache.commons.fileupload``, ``org.apache.commons.httpclient.util``, ``org.apache.commons.jelly``, ``org.apache.commons.jexl2``, ``org.apache.commons.jexl3``, ``org.apache.commons.lang``, ``org.apache.commons.logging``, ``org.apache.commons.net``, ``org.apache.commons.ognl``, ``org.apache.cxf.catalog``, ``org.apache.cxf.common.classloader``, ``org.apache.cxf.common.jaxb``, ``org.apache.cxf.common.logging``, ``org.apache.cxf.configuration.jsse``, ``org.apache.cxf.helpers``, ``org.apache.cxf.resource``, ``org.apache.cxf.staxutils``, ``org.apache.cxf.tools.corba.utils``, ``org.apache.cxf.tools.util``, ``org.apache.cxf.transform``, ``org.apache.directory.ldap.client.api``, ``org.apache.hadoop.fs``, ``org.apache.hadoop.hive.metastore``, ``org.apache.hadoop.hive.ql.exec``, ``org.apache.hadoop.hive.ql.metadata``, ``org.apache.hc.client5.http.async.methods``, ``org.apache.hc.client5.http.classic.methods``, ``org.apache.hc.client5.http.fluent``, ``org.apache.hive.hcatalog.templeton``, ``org.apache.ibatis.jdbc``, ``org.apache.ibatis.mapping``, ``org.apache.log4j``, ``org.apache.shiro.authc``, ``org.apache.shiro.codec``, ``org.apache.shiro.jndi``, ``org.apache.shiro.mgt``, ``org.apache.sshd.client.session``, ``org.apache.tools.ant``, ``org.apache.tools.zip``, ``org.codehaus.cargo.container.installer``, ``org.dom4j``, ``org.exolab.castor.xml``, ``org.fusesource.leveldbjni``, ``org.geogebra.web.full.main``, ``org.gradle.api.file``, ``org.ho.yaml``, ``org.influxdb``, ``org.jabsorb``, ``org.jboss.vfs``, ``org.jdbi.v3.core``, ``org.jenkins.ui.icon``, ``org.jenkins.ui.symbol``, ``org.keycloak.models.map.storage``, ``org.kohsuke.stapler``, ``org.lastaflute.web``, ``org.mvel2``, ``org.openjdk.jmh.runner.options``, ``org.owasp.esapi``, ``org.pac4j.jwt.config.encryption``, ``org.pac4j.jwt.config.signature``, ``org.scijava.log``, ``org.xml.sax``, ``org.xmlpull.v1``, ``play.libs.ws``, ``play.mvc``, ``ratpack.core.form``, ``ratpack.core.handling``, ``ratpack.core.http``, ``ratpack.exec``, ``ratpack.form``, ``ratpack.func``, ``ratpack.handling``, ``ratpack.http``, ``ratpack.util``, ``software.amazon.awssdk.transfer.s3.model``, ``sun.jvmstat.perfdata.monitor.protocol.local``, ``sun.jvmstat.perfdata.monitor.protocol.rmi``, ``sun.misc``, ``sun.net.ftp``, ``sun.net.www.protocol.http``, ``sun.security.acl``, ``sun.security.jgss.krb5``, ``sun.security.krb5``, ``sun.security.pkcs``, ``sun.security.pkcs11``, ``sun.security.provider``, ``sun.security.ssl``, ``sun.security.x509``, ``sun.tools.jconsole``",127,6034,775,148,6,14,18,,186
|
||||
Totals,,382,26381,2702,421,16,137,33,1,410
|
||||
Totals,,382,26403,2707,421,16,137,33,1,415
|
||||
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
---
|
||||
category: minorAnalysis
|
||||
---
|
||||
* Improved modeling of Apache HttpClient `execute` method sinks for `java/ssrf` and `java/non-https-url`.
|
||||
@@ -11,7 +11,7 @@ extensions:
|
||||
- ["org.apache.http.client.methods", "HttpPost", False, "HttpPost", "", "", "Argument[0]", "request-forgery", "manual"]
|
||||
- ["org.apache.http.client.methods", "HttpPut", False, "HttpPut", "", "", "Argument[0]", "request-forgery", "manual"]
|
||||
- ["org.apache.http.client.methods", "HttpRequestBase", True, "setURI", "", "", "Argument[0]", "request-forgery", "manual"]
|
||||
- ["org.apache.http.client.methods", "HttpRequestWrapper", True, "setURI", "(URI)", "", "Argument[0]", "request-forgery", "hq-manual"]
|
||||
- ["org.apache.http.client.methods", "HttpRequestWrapper", True, "setURI", "(URI)", "", "Argument[0]", "request-forgery", "ai-manual"]
|
||||
- ["org.apache.http.client.methods", "HttpTrace", False, "HttpTrace", "", "", "Argument[0]", "request-forgery", "manual"]
|
||||
- ["org.apache.http.client.methods", "RequestBuilder", False, "delete", "", "", "Argument[0]", "request-forgery", "manual"]
|
||||
- ["org.apache.http.client.methods", "RequestBuilder", False, "get", "", "", "Argument[0]", "request-forgery", "manual"]
|
||||
@@ -22,3 +22,29 @@ extensions:
|
||||
- ["org.apache.http.client.methods", "RequestBuilder", False, "put", "", "", "Argument[0]", "request-forgery", "manual"]
|
||||
- ["org.apache.http.client.methods", "RequestBuilder", False, "setUri", "", "", "Argument[0]", "request-forgery", "manual"]
|
||||
- ["org.apache.http.client.methods", "RequestBuilder", False, "trace", "", "", "Argument[0]", "request-forgery", "manual"]
|
||||
- addsTo:
|
||||
pack: codeql/java-all
|
||||
extensible: summaryModel
|
||||
data:
|
||||
- ["org.apache.http.client.methods", "RequestBuilder", True, "build", "()", "", "Argument[this]", "ReturnValue", "taint", "ai-manual"]
|
||||
- ["org.apache.http.client.methods", "RequestBuilder", True, "delete", "(String)", "", "Argument[0]", "ReturnValue", "taint", "ai-manual"]
|
||||
- ["org.apache.http.client.methods", "RequestBuilder", True, "delete", "(URI)", "", "Argument[0]", "ReturnValue", "taint", "ai-manual"]
|
||||
- ["org.apache.http.client.methods", "RequestBuilder", True, "get", "(String)", "", "Argument[0]", "ReturnValue", "taint", "ai-manual"]
|
||||
- ["org.apache.http.client.methods", "RequestBuilder", True, "get", "(URI)", "", "Argument[0]", "ReturnValue", "taint", "ai-manual"]
|
||||
- ["org.apache.http.client.methods", "RequestBuilder", True, "getUri", "()", "", "Argument[this]", "ReturnValue", "taint", "ai-manual"]
|
||||
- ["org.apache.http.client.methods", "RequestBuilder", True, "head", "(String)", "", "Argument[0]", "ReturnValue", "taint", "ai-manual"]
|
||||
- ["org.apache.http.client.methods", "RequestBuilder", True, "head", "(URI)", "", "Argument[0]", "ReturnValue", "taint", "ai-manual"]
|
||||
- ["org.apache.http.client.methods", "RequestBuilder", True, "options", "(String)", "", "Argument[0]", "ReturnValue", "taint", "ai-manual"]
|
||||
- ["org.apache.http.client.methods", "RequestBuilder", True, "options", "(URI)", "", "Argument[0]", "ReturnValue", "taint", "ai-manual"]
|
||||
- ["org.apache.http.client.methods", "RequestBuilder", True, "patch", "(String)", "", "Argument[0]", "ReturnValue", "taint", "ai-manual"]
|
||||
- ["org.apache.http.client.methods", "RequestBuilder", True, "patch", "(URI)", "", "Argument[0]", "ReturnValue", "taint", "ai-manual"]
|
||||
- ["org.apache.http.client.methods", "RequestBuilder", True, "post", "(String)", "", "Argument[0]", "ReturnValue", "taint", "ai-manual"]
|
||||
- ["org.apache.http.client.methods", "RequestBuilder", True, "post", "(URI)", "", "Argument[0]", "ReturnValue", "taint", "ai-manual"]
|
||||
- ["org.apache.http.client.methods", "RequestBuilder", True, "put", "(String)", "", "Argument[0]", "ReturnValue", "taint", "ai-manual"]
|
||||
- ["org.apache.http.client.methods", "RequestBuilder", True, "put", "(URI)", "", "Argument[0]", "ReturnValue", "taint", "ai-manual"]
|
||||
- ["org.apache.http.client.methods", "RequestBuilder", True, "setUri", "(String)", "", "Argument[0]", "Argument[this]", "taint", "ai-manual"]
|
||||
- ["org.apache.http.client.methods", "RequestBuilder", True, "setUri", "(String)", "", "Argument[0]", "ReturnValue", "taint", "ai-manual"]
|
||||
- ["org.apache.http.client.methods", "RequestBuilder", True, "setUri", "(URI)", "", "Argument[0]", "Argument[this]", "taint", "ai-manual"]
|
||||
- ["org.apache.http.client.methods", "RequestBuilder", True, "setUri", "(URI)", "", "Argument[0]", "ReturnValue", "taint", "ai-manual"]
|
||||
- ["org.apache.http.client.methods", "RequestBuilder", True, "trace", "(String)", "", "Argument[0]", "ReturnValue", "taint", "ai-manual"]
|
||||
- ["org.apache.http.client.methods", "RequestBuilder", True, "trace", "(URI)", "", "Argument[0]", "ReturnValue", "taint", "ai-manual"]
|
||||
|
||||
@@ -3,6 +3,11 @@ extensions:
|
||||
pack: codeql/java-all
|
||||
extensible: sinkModel
|
||||
data:
|
||||
- ["org.apache.http.client", "HttpClient", True, "execute", "(HttpUriRequest,HttpContext)", "", "Argument[0]", "request-forgery", "ai-manual"]
|
||||
- ["org.apache.http.client", "HttpClient", True, "execute", "(HttpUriRequest,ResponseHandler,HttpContext)", "", "Argument[0]", "request-forgery", "ai-manual"]
|
||||
- ["org.apache.http.client", "HttpClient", True, "execute", "(HttpHost,HttpRequest)", "", "Argument[0]", "request-forgery", "ai-manual"]
|
||||
- ["org.apache.http.client", "HttpClient", True, "execute", "(HttpHost,HttpRequest,HttpContext)", "", "Argument[0]", "request-forgery", "ai-manual"]
|
||||
- ["org.apache.http.client", "HttpClient", True, "execute", "(HttpHost,HttpRequest,ResponseHandler)", "", "Argument[0]", "request-forgery", "ai-manual"]
|
||||
- ["org.apache.http.client", "HttpClient", True, "execute", "(HttpHost,HttpRequest,ResponseHandler,HttpContext)", "", "Argument[0]", "request-forgery", "ai-manual"]
|
||||
- ["org.apache.http.client", "HttpClient", True, "execute", "(HttpUriRequest)", "", "Argument[0]", "request-forgery", "ai-manual"]
|
||||
- ["org.apache.http.client", "HttpClient", True, "execute", "(HttpUriRequest,HttpContext)", "", "Argument[0]", "request-forgery", "ai-manual"]
|
||||
- ["org.apache.http.client", "HttpClient", True, "execute", "(HttpUriRequest,ResponseHandler)", "", "Argument[0]", "request-forgery", "ai-manual"]
|
||||
- ["org.apache.http.client", "HttpClient", True, "execute", "(HttpUriRequest,ResponseHandler,HttpContext)", "", "Argument[0]", "request-forgery", "ai-manual"]
|
||||
|
||||
@@ -4,67 +4,33 @@
|
||||
overlay[local?]
|
||||
module;
|
||||
|
||||
private import internal.rangeanalysis.BoundSpecific
|
||||
private import java as J
|
||||
private import semmle.code.java.dataflow.SSA
|
||||
private import semmle.code.java.dataflow.RangeUtils as RU
|
||||
private import codeql.rangeanalysis.Bound as SharedBound
|
||||
|
||||
private newtype TBound =
|
||||
TBoundZero() or
|
||||
TBoundSsa(SsaVariable v) { v.getSourceVariable().getType() instanceof IntegralType } or
|
||||
TBoundExpr(Expr e) {
|
||||
interestingExprBound(e) and
|
||||
not exists(SsaVariable v | e = v.getAUse())
|
||||
private module BoundDefs implements SharedBound::BoundDefinitions<J::Location> {
|
||||
class SsaVariable extends Ssa::SsaDefinition {
|
||||
/** Gets a use of this variable. */
|
||||
Expr getAUse() { result = super.getARead() }
|
||||
}
|
||||
|
||||
/**
|
||||
* A bound that may be inferred for an expression plus/minus an integer delta.
|
||||
*/
|
||||
abstract class Bound extends TBound {
|
||||
/** Gets a textual representation of this bound. */
|
||||
abstract string toString();
|
||||
class SsaSourceVariable = Ssa::SourceVariable;
|
||||
|
||||
/** Gets an expression that equals this bound plus `delta`. */
|
||||
abstract Expr getExpr(int delta);
|
||||
class Type = J::Type;
|
||||
|
||||
/** Gets an expression that equals this bound. */
|
||||
Expr getExpr() { result = this.getExpr(0) }
|
||||
class Expr = J::Expr;
|
||||
|
||||
/** Gets the location of this bound. */
|
||||
abstract Location getLocation();
|
||||
class IntegralType = J::IntegralType;
|
||||
|
||||
class ConstantIntegerExpr = RU::ConstantIntegerExpr;
|
||||
|
||||
/** Holds if `e` is a bound expression and it is not an SSA variable read. */
|
||||
predicate interestingExprBound(Expr e) {
|
||||
e.(J::FieldRead).getField() instanceof J::ArrayLengthField
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The bound that corresponds to the integer 0. This is used to represent all
|
||||
* integer bounds as bounds are always accompanied by an added integer delta.
|
||||
*/
|
||||
class ZeroBound extends Bound, TBoundZero {
|
||||
override string toString() { result = "0" }
|
||||
module BoundImpl = SharedBound::Bound<J::Location, BoundDefs>;
|
||||
|
||||
override Expr getExpr(int delta) { result.(ConstantIntegerExpr).getIntValue() = delta }
|
||||
|
||||
override Location getLocation() { result.hasLocationInfo("", 0, 0, 0, 0) }
|
||||
}
|
||||
|
||||
/**
|
||||
* A bound corresponding to the value of an SSA variable.
|
||||
*/
|
||||
class SsaBound extends Bound, TBoundSsa {
|
||||
/** Gets the SSA variable that equals this bound. */
|
||||
SsaVariable getSsa() { this = TBoundSsa(result) }
|
||||
|
||||
override string toString() { result = this.getSsa().toString() }
|
||||
|
||||
override Expr getExpr(int delta) { result = this.getSsa().getAUse() and delta = 0 }
|
||||
|
||||
override Location getLocation() { result = this.getSsa().getLocation() }
|
||||
}
|
||||
|
||||
/**
|
||||
* A bound that corresponds to the value of a specific expression that might be
|
||||
* interesting, but isn't otherwise represented by the value of an SSA variable.
|
||||
*/
|
||||
class ExprBound extends Bound, TBoundExpr {
|
||||
override string toString() { result = this.getExpr().toString() }
|
||||
|
||||
override Expr getExpr(int delta) { this = TBoundExpr(result) and delta = 0 }
|
||||
|
||||
override Location getLocation() { result = this.getExpr().getLocation() }
|
||||
}
|
||||
import BoundImpl
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
/**
|
||||
* Provides Java-specific definitions for bounds.
|
||||
*/
|
||||
overlay[local?]
|
||||
module;
|
||||
|
||||
private import java as J
|
||||
private import semmle.code.java.dataflow.SSA as Ssa
|
||||
private import semmle.code.java.dataflow.RangeUtils as RU
|
||||
|
||||
class SsaVariable extends Ssa::SsaDefinition {
|
||||
/** Gets a use of this variable. */
|
||||
Expr getAUse() { result = super.getARead() }
|
||||
}
|
||||
|
||||
class Expr = J::Expr;
|
||||
|
||||
class Location = J::Location;
|
||||
|
||||
class IntegralType = J::IntegralType;
|
||||
|
||||
class ConstantIntegerExpr = RU::ConstantIntegerExpr;
|
||||
|
||||
/** Holds if `e` is a bound expression and it is not an SSA variable read. */
|
||||
predicate interestingExprBound(Expr e) {
|
||||
e.(J::FieldRead).getField() instanceof J::ArrayLengthField
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
import java.io.IOException;
|
||||
|
||||
import org.apache.http.HttpHost;
|
||||
import org.apache.http.HttpRequest;
|
||||
import org.apache.http.client.HttpClient;
|
||||
import org.apache.http.client.ResponseHandler;
|
||||
import org.apache.http.client.methods.HttpUriRequest;
|
||||
import org.apache.http.client.methods.RequestBuilder;
|
||||
import org.apache.http.impl.client.HttpClients;
|
||||
import org.apache.http.message.BasicHttpRequest;
|
||||
import org.apache.http.protocol.HttpContext;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServlet;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
public class ApacheHttpClientExecuteSSRF extends HttpServlet {
|
||||
|
||||
protected void doGet(HttpServletRequest request, HttpServletResponse response)
|
||||
throws ServletException, IOException {
|
||||
try {
|
||||
|
||||
String source = request.getParameter("host"); // $ Source
|
||||
|
||||
HttpHost host = new HttpHost(source);
|
||||
HttpRequest req = new BasicHttpRequest("GET", "/");
|
||||
HttpUriRequest uriReq = RequestBuilder.get(source).build(); // $ Alert
|
||||
HttpContext context = null;
|
||||
HttpClient client = HttpClients.createDefault();
|
||||
ResponseHandler<Object> handler = null;
|
||||
|
||||
client.execute(host, req); // $ Alert
|
||||
client.execute(host, req, context); // $ Alert
|
||||
client.execute(host, req, handler); // $ Alert
|
||||
client.execute(host, req, handler, context); // $ Alert
|
||||
client.execute(uriReq); // $ Alert
|
||||
client.execute(uriReq, context); // $ Alert
|
||||
client.execute(uriReq, handler); // $ Alert
|
||||
client.execute(uriReq, handler, context); // $ Alert
|
||||
|
||||
} catch (Exception e) {
|
||||
// TODO: handle exception
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1 +1 @@
|
||||
//semmle-extractor-options: --javac-args -source 11 -target 11 -cp ${testdir}/../../../stubs/javax-validation-constraints:${testdir}/../../../stubs/springframework-5.8.x:${testdir}/../../../stubs/javax-ws-rs-api-2.1.1:${testdir}/../../../stubs/javax-ws-rs-api-3.0.0:${testdir}/../../../stubs/apache-http-4.4.13/:${testdir}/../../../stubs/projectreactor-3.4.3/:${testdir}/../../../stubs/postgresql-42.3.3/:${testdir}/../../../stubs/HikariCP-3.4.5/:${testdir}/../../../stubs/spring-jdbc-5.3.8/:${testdir}/../../../stubs/jdbi3-core-3.27.2/:${testdir}/../../../stubs/cargo:${testdir}/../../../stubs/javafx-web:${testdir}/../../../stubs/apache-commons-jelly-1.0.1:${testdir}/../../../stubs/dom4j-2.1.1:${testdir}/../../../stubs/jaxen-1.2.0:${testdir}/../../../stubs/stapler-1.263:${testdir}/../../../stubs/javax-servlet-2.5:${testdir}/../../../stubs/apache-commons-fileupload-1.4:${testdir}/../../../stubs/saxon-xqj-9.x:${testdir}/../../../stubs/apache-commons-beanutils:${testdir}/../../../stubs/apache-commons-lang:${testdir}/../../../stubs/apache-http-5:${testdir}/../../../stubs/playframework-2.6.x:${testdir}/../../../stubs/jaxws-api-2.0:${testdir}/../../../stubs/apache-cxf
|
||||
//semmle-extractor-options: --javac-args -source 11 -target 11 -cp ${testdir}/../../../stubs/javax-validation-constraints:${testdir}/../../../stubs/springframework-5.8.x:${testdir}/../../../stubs/javax-ws-rs-api-2.1.1:${testdir}/../../../stubs/javax-ws-rs-api-3.0.0:${testdir}/../../../stubs/apache-http-4.4.13/:${testdir}/../../../stubs/apache-http-client-4.4.13:${testdir}/../../../stubs/projectreactor-3.4.3/:${testdir}/../../../stubs/postgresql-42.3.3/:${testdir}/../../../stubs/HikariCP-3.4.5/:${testdir}/../../../stubs/spring-jdbc-5.3.8/:${testdir}/../../../stubs/jdbi3-core-3.27.2/:${testdir}/../../../stubs/cargo:${testdir}/../../../stubs/javafx-web:${testdir}/../../../stubs/apache-commons-jelly-1.0.1:${testdir}/../../../stubs/dom4j-2.1.1:${testdir}/../../../stubs/jaxen-1.2.0:${testdir}/../../../stubs/stapler-1.263:${testdir}/../../../stubs/javax-servlet-2.5:${testdir}/../../../stubs/apache-commons-fileupload-1.4:${testdir}/../../../stubs/saxon-xqj-9.x:${testdir}/../../../stubs/apache-commons-beanutils:${testdir}/../../../stubs/apache-commons-lang:${testdir}/../../../stubs/apache-http-5:${testdir}/../../../stubs/playframework-2.6.x:${testdir}/../../../stubs/jaxws-api-2.0:${testdir}/../../../stubs/apache-cxf
|
||||
|
||||
23
java/ql/test/stubs/apache-http-client-4.4.13/org/apache/http/client/HttpClient.java
generated
Normal file
23
java/ql/test/stubs/apache-http-client-4.4.13/org/apache/http/client/HttpClient.java
generated
Normal file
@@ -0,0 +1,23 @@
|
||||
// Generated automatically from org.apache.http.client.HttpClient for testing purposes
|
||||
|
||||
package org.apache.http.client;
|
||||
|
||||
import java.io.IOException;
|
||||
import org.apache.http.HttpHost;
|
||||
import org.apache.http.HttpRequest;
|
||||
import org.apache.http.HttpResponse;
|
||||
import org.apache.http.client.methods.HttpUriRequest;
|
||||
import org.apache.http.protocol.HttpContext;
|
||||
|
||||
public interface HttpClient {
|
||||
HttpResponse execute(HttpHost target, HttpRequest request) throws IOException;
|
||||
HttpResponse execute(HttpHost target, HttpRequest request, HttpContext context) throws IOException;
|
||||
<T> T execute(HttpHost target, HttpRequest request, ResponseHandler<? extends T> responseHandler) throws IOException;
|
||||
<T> T execute(HttpHost target, HttpRequest request, ResponseHandler<? extends T> responseHandler, HttpContext context)
|
||||
throws IOException;
|
||||
HttpResponse execute(HttpUriRequest request) throws IOException;
|
||||
HttpResponse execute(HttpUriRequest request, HttpContext context) throws IOException;
|
||||
<T> T execute(HttpUriRequest request, ResponseHandler<? extends T> responseHandler) throws IOException;
|
||||
<T> T execute(HttpUriRequest request, ResponseHandler<? extends T> responseHandler, HttpContext context)
|
||||
throws IOException;
|
||||
}
|
||||
9
java/ql/test/stubs/apache-http-client-4.4.13/org/apache/http/client/ResponseHandler.java
generated
Normal file
9
java/ql/test/stubs/apache-http-client-4.4.13/org/apache/http/client/ResponseHandler.java
generated
Normal file
@@ -0,0 +1,9 @@
|
||||
// Generated automatically from org.apache.http.client.ResponseHandler for testing purposes
|
||||
|
||||
package org.apache.http.client;
|
||||
|
||||
import org.apache.http.HttpResponse;
|
||||
|
||||
public interface ResponseHandler<T> {
|
||||
T handleResponse(HttpResponse response);
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package org.apache.http.impl.client;
|
||||
|
||||
import org.apache.http.client.HttpClient;
|
||||
|
||||
public abstract class CloseableHttpClient implements HttpClient {
|
||||
|
||||
}
|
||||
10
java/ql/test/stubs/apache-http-client-4.4.13/org/apache/http/impl/client/HttpClients.java
generated
Normal file
10
java/ql/test/stubs/apache-http-client-4.4.13/org/apache/http/impl/client/HttpClients.java
generated
Normal file
@@ -0,0 +1,10 @@
|
||||
// Generated automatically from org.apache.http.client.HttpClient for testing purposes
|
||||
|
||||
package org.apache.http.impl.client;
|
||||
|
||||
import java.io.IOException;
|
||||
import org.apache.http.impl.client.CloseableHttpClient;
|
||||
|
||||
public final class HttpClients {
|
||||
public static CloseableHttpClient createDefault() { return null; }
|
||||
}
|
||||
@@ -21,13 +21,19 @@ file_coverage_languages:
|
||||
scc_languages:
|
||||
- TypeScript
|
||||
- TypeScript Typings
|
||||
- name: vue
|
||||
display_name: Vue.js component
|
||||
scc_languages:
|
||||
- Vue
|
||||
github_api_languages:
|
||||
- JavaScript
|
||||
- TypeScript
|
||||
- Vue
|
||||
scc_languages:
|
||||
- JavaScript
|
||||
- TypeScript
|
||||
- TypeScript Typings
|
||||
- Vue
|
||||
file_types:
|
||||
- name: javascript
|
||||
display_name: JavaScript
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
import semmle.python.controlflow.internal.AstNodeImpl
|
||||
import ControlFlow::Consistency
|
||||
@@ -9,7 +9,6 @@ private import semmle.python.dataflow.new.internal.DataFlowImplSpecific
|
||||
private import semmle.python.dataflow.new.internal.DataFlowDispatch
|
||||
private import semmle.python.dataflow.new.internal.TaintTrackingImplSpecific
|
||||
private import codeql.dataflow.internal.DataFlowImplConsistency
|
||||
private import semmle.python.controlflow.internal.Cfg as Cfg
|
||||
|
||||
private module Input implements InputSig<Location, PythonDataFlow> {
|
||||
private import Private
|
||||
@@ -37,6 +36,8 @@ private module Input implements InputSig<Location, PythonDataFlow> {
|
||||
// parameter, but dataflow-consistency queries should _not_ complain about there not
|
||||
// being a post-update node for the synthetic `**kwargs` parameter.
|
||||
n instanceof SynthDictSplatParameterNode
|
||||
or
|
||||
Private::Conversions::readStep(n, _, _)
|
||||
}
|
||||
|
||||
predicate uniqueParameterNodePositionExclude(DataFlowCallable c, ParameterPosition pos, Node p) {
|
||||
@@ -73,7 +74,7 @@ private module Input implements InputSig<Location, PythonDataFlow> {
|
||||
// resolve to multiple functions), but we only make _one_ ArgumentNode for each
|
||||
// argument in the CallNode, we end up violating this consistency check in those
|
||||
// cases. (see `getCallArg` in DataFlowDispatch.qll)
|
||||
exists(DataFlowCall other, Cfg::CallNode cfgCall | other != call |
|
||||
exists(DataFlowCall other, CallNode cfgCall | other != call |
|
||||
call.getNode() = cfgCall and
|
||||
other.getNode() = cfgCall and
|
||||
isArgumentNode(arg, call, _) and
|
||||
@@ -89,16 +90,16 @@ private module Input implements InputSig<Location, PythonDataFlow> {
|
||||
// allow it instead.
|
||||
(
|
||||
call.getScope() = attr.getScope() and
|
||||
any(CfgNode n | n.asCfgNode() = call.getNode().(Cfg::CallNode).getFunction())
|
||||
.getALocalSource() = attr
|
||||
any(CfgNode n | n.asCfgNode() = call.getNode().(CallNode).getFunction()).getALocalSource() =
|
||||
attr
|
||||
or
|
||||
not exists(call.getScope().(Function).getDefinition()) and
|
||||
call.getScope().getScope+() = attr.getScope()
|
||||
) and
|
||||
(
|
||||
other.getScope() = attr.getScope() and
|
||||
any(CfgNode n | n.asCfgNode() = other.getNode().(Cfg::CallNode).getFunction())
|
||||
.getALocalSource() = attr
|
||||
any(CfgNode n | n.asCfgNode() = other.getNode().(CallNode).getFunction()).getALocalSource() =
|
||||
attr
|
||||
or
|
||||
not exists(other.getScope().(Function).getDefinition()) and
|
||||
other.getScope().getScope+() = attr.getScope()
|
||||
|
||||
@@ -213,11 +213,9 @@ class ExprWithPointsTo extends Expr {
|
||||
* Gets what this expression might "refer-to" in the given `context`.
|
||||
*/
|
||||
predicate refersTo(Context context, Object obj, ClassObject cls, AstNode origin) {
|
||||
exists(ControlFlowNode this_, ControlFlowNode origin_ |
|
||||
this_.getNode() = this and origin_.getNode() = origin
|
||||
|
|
||||
this_.(ControlFlowNodeWithPointsTo).refersTo(context, obj, cls, origin_)
|
||||
)
|
||||
this.getAFlowNode()
|
||||
.(ControlFlowNodeWithPointsTo)
|
||||
.refersTo(context, obj, cls, origin.getAFlowNode())
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -228,11 +226,7 @@ class ExprWithPointsTo extends Expr {
|
||||
*/
|
||||
pragma[nomagic]
|
||||
predicate refersTo(Object obj, AstNode origin) {
|
||||
exists(ControlFlowNode this_, ControlFlowNode origin_ |
|
||||
this_.getNode() = this and origin_.getNode() = origin
|
||||
|
|
||||
this_.(ControlFlowNodeWithPointsTo).refersTo(obj, origin_)
|
||||
)
|
||||
this.getAFlowNode().(ControlFlowNodeWithPointsTo).refersTo(obj, origin.getAFlowNode())
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -246,22 +240,16 @@ class ExprWithPointsTo extends Expr {
|
||||
* in the given `context`.
|
||||
*/
|
||||
predicate pointsTo(Context context, Value value, AstNode origin) {
|
||||
exists(ControlFlowNode this_, ControlFlowNode origin_ |
|
||||
this_.getNode() = this and origin_.getNode() = origin
|
||||
|
|
||||
this_.(ControlFlowNodeWithPointsTo).pointsTo(context, value, origin_)
|
||||
)
|
||||
this.getAFlowNode()
|
||||
.(ControlFlowNodeWithPointsTo)
|
||||
.pointsTo(context, value, origin.getAFlowNode())
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if this expression might "point-to" to `value` which is from `origin`.
|
||||
*/
|
||||
predicate pointsTo(Value value, AstNode origin) {
|
||||
exists(ControlFlowNode this_, ControlFlowNode origin_ |
|
||||
this_.getNode() = this and origin_.getNode() = origin
|
||||
|
|
||||
this_.(ControlFlowNodeWithPointsTo).pointsTo(value, origin_)
|
||||
)
|
||||
this.getAFlowNode().(ControlFlowNodeWithPointsTo).pointsTo(value, origin.getAFlowNode())
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -487,10 +475,7 @@ class FunctionMetricsWithPointsTo extends FunctionMetrics {
|
||||
not non_coupling_method(result) and
|
||||
exists(Call call | call.getScope() = this |
|
||||
exists(FunctionObject callee | callee.getFunction() = result |
|
||||
exists(CallNode call_ |
|
||||
call_.getNode() = call and
|
||||
call_.getFunction().(ControlFlowNodeWithPointsTo).refersTo(callee)
|
||||
)
|
||||
call.getAFlowNode().getFunction().(ControlFlowNodeWithPointsTo).refersTo(callee)
|
||||
)
|
||||
or
|
||||
exists(Attribute a | call.getFunc() = a |
|
||||
|
||||
@@ -64,7 +64,7 @@ private predicate jump_to_defn(ControlFlowNode use, Definition defn) {
|
||||
private predicate preferred_jump_to_defn(Expr use, Definition def) {
|
||||
not use instanceof ClassExpr and
|
||||
not use instanceof FunctionExpr and
|
||||
exists(ControlFlowNode useNode | useNode.getNode() = use | jump_to_defn(useNode, def))
|
||||
jump_to_defn(use.getAFlowNode(), def)
|
||||
}
|
||||
|
||||
private predicate unique_jump_to_defn(Expr use, Definition def) {
|
||||
@@ -452,7 +452,7 @@ private predicate self_parameter_jump_to_defn_attribute(
|
||||
* This exists primarily for testing use `getPreferredDefinition()` instead.
|
||||
*/
|
||||
Definition getADefinition(Expr use) {
|
||||
exists(ControlFlowNode useNode | useNode.getNode() = use | jump_to_defn(useNode, result)) and
|
||||
jump_to_defn(use.getAFlowNode(), result) and
|
||||
not use instanceof Call and
|
||||
not use.isArtificial() and
|
||||
// Not the use itself
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
---
|
||||
category: minorAnalysis
|
||||
---
|
||||
* A new Python control flow graph implementation has been added under `semmle.python.controlflow.internal.Cfg` (backed by `AstNodeImpl.qll`), built on the shared `codeql.controlflow.ControlFlowGraph` library. It is not yet used by the dataflow library or any production query; the legacy CFG in `semmle/python/Flow.qll` remains the default. The new library is exposed for tests and for upcoming migrations.
|
||||
@@ -1,4 +0,0 @@
|
||||
---
|
||||
category: minorAnalysis
|
||||
---
|
||||
* A new SSA adapter has been added under `semmle.python.dataflow.new.internal.SsaImpl`, built on the shared `codeql.ssa.Ssa` library and the new shared CFG (`semmle.python.controlflow.internal.Cfg`). It is not yet used by the dataflow library or any production query; the legacy ESSA SSA in `semmle/python/essa/*` remains the default. The new SSA adapter is exposed for tests and for the upcoming dataflow migration.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
category: deprecated
|
||||
---
|
||||
* The `AstNode.getAFlowNode()` predicate has been deprecated. Use `ControlFlowNode.getNode()` from the other direction instead: replace `e.getAFlowNode() = n` with `n.getNode() = e`. This is a preparatory step towards migrating the dataflow library off the legacy CFG; it has no semantic effect.
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
---
|
||||
category: minorAnalysis
|
||||
---
|
||||
* Python taint tracking is now more precise for values flowing through container contents, such as list, set, tuple, and dictionary elements. This may remove some false positive alerts.
|
||||
@@ -1,4 +0,0 @@
|
||||
---
|
||||
category: minorAnalysis
|
||||
---
|
||||
* Simplified the internal predicates that detect `@staticmethod`, `@classmethod` and `@property` decorators to match the decorator's AST `Name` directly, rather than going through the CFG and requiring the name to resolve globally. Code that shadows these three builtin decorators at the module-scope will now be classified by the decorator name alone; in practice, shadowing these names is extremely rare and the call-graph results are unchanged.
|
||||
@@ -1,4 +0,0 @@
|
||||
---
|
||||
category: deprecated
|
||||
---
|
||||
* The `Function.getAReturnValueFlowNode()` predicate has been deprecated. Bind a `Return` node explicitly instead — `exists(Return ret | ret.getScope() = f and n.getNode() = ret.getValue())`. This is a preparatory step towards migrating the dataflow library off the legacy CFG; it has no semantic effect.
|
||||
@@ -1,4 +0,0 @@
|
||||
---
|
||||
category: breaking
|
||||
---
|
||||
* The deprecated `AstNode.getAFlowNode()` and `Function.getAReturnValueFlowNode()` predicates now return nodes from the new shared CFG (`Cfg::ControlFlowNode`) rather than from the legacy CFG (`ControlFlowNode`). Callers that still rely on these deprecated APIs and feed the result into legacy-CFG-aware predicates will no longer type-check; migrate to `n.getNode() = e` (or, for return values, the explicit `Return` pattern shown in the deprecation message) to get nodes from the dataflow library's current CFG.
|
||||
@@ -1,4 +0,0 @@
|
||||
---
|
||||
category: minorAnalysis
|
||||
---
|
||||
* The new (shared-CFG-based) Python control flow graph now visits parameter and return type annotations as CFG nodes for function definitions, matching the legacy CFG. This restores annotation-based type tracking through framework models such as FastAPI's `Depends()`, Pydantic request models, Starlette `WebSocket` handlers, and any other models that flow a class reference through `Parameter.getAnnotation()` to identify instances of the annotated class.
|
||||
@@ -1,45 +0,0 @@
|
||||
/**
|
||||
* @name Print CFG (New)
|
||||
* @description Produces a representation of a file's Control Flow Graph
|
||||
* using the new shared control flow library.
|
||||
* This query is used by the VS Code extension.
|
||||
* @id python/print-cfg
|
||||
* @kind graph
|
||||
* @tags ide-contextual-queries/print-cfg
|
||||
*/
|
||||
|
||||
private import python as Py
|
||||
import semmle.python.controlflow.internal.AstNodeImpl
|
||||
|
||||
external string selectedSourceFile();
|
||||
|
||||
private predicate selectedSourceFileAlias = selectedSourceFile/0;
|
||||
|
||||
external int selectedSourceLine();
|
||||
|
||||
private predicate selectedSourceLineAlias = selectedSourceLine/0;
|
||||
|
||||
external int selectedSourceColumn();
|
||||
|
||||
private predicate selectedSourceColumnAlias = selectedSourceColumn/0;
|
||||
|
||||
module ViewCfgQueryInput implements ControlFlow::ViewCfgQueryInputSig<Py::File> {
|
||||
predicate selectedSourceFile = selectedSourceFileAlias/0;
|
||||
|
||||
predicate selectedSourceLine = selectedSourceLineAlias/0;
|
||||
|
||||
predicate selectedSourceColumn = selectedSourceColumnAlias/0;
|
||||
|
||||
predicate cfgScopeSpan(
|
||||
Ast::Callable callable, Py::File file, int startLine, int startColumn, int endLine,
|
||||
int endColumn
|
||||
) {
|
||||
exists(Py::Scope scope |
|
||||
scope = callable.asScope() and
|
||||
file = scope.getLocation().getFile() and
|
||||
scope.getLocation().hasLocationInfo(_, startLine, startColumn, endLine, endColumn)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
import ControlFlow::ViewCfgQuery<Py::File, ViewCfgQueryInput>
|
||||
@@ -6,9 +6,8 @@
|
||||
* directed and labeled; they specify how the components represented by nodes relate to each other.
|
||||
*/
|
||||
|
||||
// Importing python under the `PY` namespace to avoid pulling in `CallNode` from `Flow.qll` (via `import python`) and thereby having a naming conflict with `API::CallNode`.
|
||||
// Importing python under the `py` namespace to avoid importing `CallNode` from `Flow.qll` and thereby having a naming conflict with `API::CallNode`.
|
||||
private import python as PY
|
||||
private import semmle.python.controlflow.internal.Cfg as Cfg
|
||||
import semmle.python.dataflow.new.DataFlow
|
||||
private import semmle.python.internal.CachedStages
|
||||
|
||||
@@ -283,7 +282,7 @@ module API {
|
||||
index = this.getIndex() and
|
||||
(
|
||||
// subscripting
|
||||
exists(Cfg::SubscriptNode subscript |
|
||||
exists(PY::SubscriptNode subscript |
|
||||
subscript.getObject() = this.getAValueReachableFromSource().asCfgNode() and
|
||||
subscript.getIndex() = index.asSink().asCfgNode()
|
||||
|
|
||||
@@ -291,7 +290,7 @@ module API {
|
||||
subscript = result.asSource().asCfgNode()
|
||||
or
|
||||
// writing
|
||||
subscript.(Cfg::DefinitionNode).getValue() = result.asSink().asCfgNode()
|
||||
subscript.(PY::DefinitionNode).getValue() = result.asSink().asCfgNode()
|
||||
)
|
||||
or
|
||||
// dictionary literals
|
||||
@@ -685,7 +684,7 @@ module API {
|
||||
* Ignores relative imports, such as `from ..foo.bar import baz`.
|
||||
*/
|
||||
private predicate imports(DataFlow::CfgNode imp, string name) {
|
||||
exists(Cfg::ImportExprNode iexpr |
|
||||
exists(PY::ImportExprNode iexpr |
|
||||
imp.getNode() = iexpr and
|
||||
not iexpr.getNode().isRelative() and
|
||||
name = iexpr.getNode().getImportedModuleName()
|
||||
@@ -776,7 +775,7 @@ module API {
|
||||
// list literals, from `x` to `[x]`
|
||||
// TODO: once convenient, this should be done at a higher level than the AST,
|
||||
// at least at the CFG layer, to take splitting into account.
|
||||
// Also consider `Cfg::SequenceNode` for generality.
|
||||
// Also consider `SequenceNode for generality.
|
||||
exists(PY::List list | list = pred.(DataFlow::ExprNode).getNode().getNode() |
|
||||
rhs.(DataFlow::ExprNode).getNode().getNode() = list.getAnElt() and
|
||||
lbl = Label::subscript()
|
||||
@@ -806,7 +805,7 @@ module API {
|
||||
subscript = trackUseNode(src).getSubscript(index)
|
||||
|
|
||||
// from `x` to a definition of `x[...]`
|
||||
rhs.asCfgNode() = subscript.asCfgNode().(Cfg::DefinitionNode).getValue() and
|
||||
rhs.asCfgNode() = subscript.asCfgNode().(PY::DefinitionNode).getValue() and
|
||||
lbl = Label::subscript()
|
||||
or
|
||||
// from `x` to `"key"` in `x["key"]`
|
||||
|
||||
@@ -3,7 +3,6 @@ module;
|
||||
|
||||
import python
|
||||
private import semmle.python.internal.CachedStages
|
||||
private import semmle.python.controlflow.internal.Cfg as Cfg
|
||||
|
||||
/** A syntactic node (Class, Function, Module, Expr, Stmt or Comprehension) corresponding to a flow node */
|
||||
abstract class AstNode extends AstNode_ {
|
||||
@@ -17,25 +16,21 @@ abstract class AstNode extends AstNode_ {
|
||||
/** Gets the scope that this node occurs in */
|
||||
abstract Scope getScope();
|
||||
|
||||
/**
|
||||
* Gets a flow node corresponding directly to this node.
|
||||
* NOTE: For some statements and other purely syntactic elements,
|
||||
* there may not be a `ControlFlowNode`
|
||||
*/
|
||||
cached
|
||||
ControlFlowNode getAFlowNode() {
|
||||
Stages::AST::ref() and
|
||||
py_flow_bb_node(result, this, _, _)
|
||||
}
|
||||
|
||||
/** Gets the location for this AST node */
|
||||
cached
|
||||
Location getLocation() { none() }
|
||||
|
||||
/**
|
||||
* DEPRECATED: use `ControlFlowNode.getNode()` from the other direction instead;
|
||||
* that is, replace `e.getAFlowNode() = n` with `n.getNode() = e`. This API is
|
||||
* being removed to untangle the AST and CFG hierarchies.
|
||||
*
|
||||
* Gets a flow node corresponding directly to this node, from the new
|
||||
* (shared) CFG. NOTE: For some statements and other purely syntactic
|
||||
* elements, there may not be a `ControlFlowNode`.
|
||||
*/
|
||||
cached
|
||||
deprecated Cfg::ControlFlowNode getAFlowNode() {
|
||||
Stages::AST::ref() and
|
||||
result.getNode() = this
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether this syntactic element is artificial, that is it is generated
|
||||
* by the compiler and is not present in the source
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
*/
|
||||
|
||||
private import python
|
||||
private import semmle.python.controlflow.internal.Cfg as Cfg
|
||||
private import semmle.python.dataflow.new.DataFlow
|
||||
private import semmle.python.dataflow.new.internal.DataFlowImplSpecific
|
||||
private import semmle.python.dataflow.new.RemoteFlowSources
|
||||
@@ -215,7 +214,7 @@ module Path {
|
||||
SafeAccessCheck() { this = DataFlow::BarrierGuard<safeAccessCheck/3>::getABarrierNode() }
|
||||
}
|
||||
|
||||
private predicate safeAccessCheck(DataFlow::GuardNode g, Cfg::ControlFlowNode node, boolean branch) {
|
||||
private predicate safeAccessCheck(DataFlow::GuardNode g, ControlFlowNode node, boolean branch) {
|
||||
g.(SafeAccessCheck::Range).checks(node, branch)
|
||||
}
|
||||
|
||||
@@ -224,7 +223,7 @@ module Path {
|
||||
/** A data-flow node that checks that a path is safe to access in some way, for example by having a controlled prefix. */
|
||||
abstract class Range extends DataFlow::GuardNode {
|
||||
/** Holds if this guard validates `node` upon evaluating to `branch`. */
|
||||
abstract predicate checks(Cfg::ControlFlowNode node, boolean branch);
|
||||
abstract predicate checks(ControlFlowNode node, boolean branch);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,9 +28,7 @@ class Expr extends Expr_, AstNode {
|
||||
/** Whether this expression may have a side effect (as determined purely from its syntax) */
|
||||
predicate hasSideEffects() {
|
||||
/* If an exception raised by this expression handled, count that as a side effect */
|
||||
exists(ControlFlowNode n | n.getNode() = this |
|
||||
n.getASuccessor().getNode() instanceof ExceptStmt
|
||||
)
|
||||
this.getAFlowNode().getASuccessor().getNode() instanceof ExceptStmt
|
||||
or
|
||||
this.getASubExpression().hasSideEffects()
|
||||
}
|
||||
@@ -70,6 +68,8 @@ class Attribute extends Attribute_ {
|
||||
/* syntax: Expr.name */
|
||||
override Expr getASubExpression() { result = this.getObject() }
|
||||
|
||||
override AttrNode getAFlowNode() { result = super.getAFlowNode() }
|
||||
|
||||
/** Gets the name of this attribute. That is the `name` in `obj.name` */
|
||||
string getName() { result = Attribute_.super.getAttr() }
|
||||
|
||||
@@ -96,6 +96,8 @@ class Subscript extends Subscript_ {
|
||||
}
|
||||
|
||||
Expr getObject() { result = Subscript_.super.getValue() }
|
||||
|
||||
override SubscriptNode getAFlowNode() { result = super.getAFlowNode() }
|
||||
}
|
||||
|
||||
/** A call expression, such as `func(...)` */
|
||||
@@ -111,6 +113,8 @@ class Call extends Call_ {
|
||||
|
||||
override string toString() { result = this.getFunc().toString() + "()" }
|
||||
|
||||
override CallNode getAFlowNode() { result = super.getAFlowNode() }
|
||||
|
||||
/** Gets a tuple (*) argument of this call. */
|
||||
Expr getStarargs() { result = this.getAPositionalArg().(Starred).getValue() }
|
||||
|
||||
@@ -196,6 +200,8 @@ class IfExp extends IfExp_ {
|
||||
override Expr getASubExpression() {
|
||||
result = this.getTest() or result = this.getBody() or result = this.getOrelse()
|
||||
}
|
||||
|
||||
override IfExprNode getAFlowNode() { result = super.getAFlowNode() }
|
||||
}
|
||||
|
||||
/** A starred expression, such as the `*rest` in the assignment `first, *rest = seq` */
|
||||
@@ -404,6 +410,8 @@ class PlaceHolder extends PlaceHolder_ {
|
||||
override Expr getASubExpression() { none() }
|
||||
|
||||
override string toString() { result = "$" + this.getId() }
|
||||
|
||||
override NameNode getAFlowNode() { result = super.getAFlowNode() }
|
||||
}
|
||||
|
||||
/** A tuple expression such as `( 1, 3, 5, 7, 9 )` */
|
||||
@@ -470,6 +478,8 @@ class Name extends Name_ {
|
||||
|
||||
override string toString() { result = this.getId() }
|
||||
|
||||
override NameNode getAFlowNode() { result = super.getAFlowNode() }
|
||||
|
||||
override predicate isArtificial() {
|
||||
/* Artificial variable names in comprehensions all start with "." */
|
||||
this.getId().charAt(0) = "."
|
||||
@@ -575,6 +585,8 @@ abstract class NameConstant extends Name, ImmutableLiteral {
|
||||
|
||||
override predicate isConstant() { any() }
|
||||
|
||||
override NameConstantNode getAFlowNode() { result = Name.super.getAFlowNode() }
|
||||
|
||||
override predicate isArtificial() { none() }
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
overlay[local]
|
||||
module;
|
||||
|
||||
import python as Py
|
||||
import python
|
||||
private import semmle.python.internal.CachedStages
|
||||
private import codeql.controlflow.BasicBlock as BB
|
||||
|
||||
@@ -17,7 +17,7 @@ private import codeql.controlflow.BasicBlock as BB
|
||||
*/
|
||||
|
||||
private predicate augstore(ControlFlowNode load, ControlFlowNode store) {
|
||||
exists(Py::Expr load_store | exists(Py::AugAssign aa | aa.getTarget() = load_store) |
|
||||
exists(Expr load_store | exists(AugAssign aa | aa.getTarget() = load_store) |
|
||||
toAst(load) = load_store and
|
||||
toAst(store) = load_store and
|
||||
load.strictlyDominates(store)
|
||||
@@ -25,7 +25,7 @@ private predicate augstore(ControlFlowNode load, ControlFlowNode store) {
|
||||
}
|
||||
|
||||
/** A non-dispatched getNode() to avoid negative recursion issues */
|
||||
private Py::AstNode toAst(ControlFlowNode n) { py_flow_bb_node(n, result, _, _) }
|
||||
private AstNode toAst(ControlFlowNode n) { py_flow_bb_node(n, result, _, _) }
|
||||
|
||||
/**
|
||||
* A control flow node. Control flow nodes have a many-to-one relation with syntactic nodes,
|
||||
@@ -35,19 +35,19 @@ private Py::AstNode toAst(ControlFlowNode n) { py_flow_bb_node(n, result, _, _)
|
||||
class ControlFlowNode extends @py_flow_node {
|
||||
/** Whether this control flow node is a load (including those in augmented assignments) */
|
||||
predicate isLoad() {
|
||||
exists(Py::Expr e | e = toAst(this) | py_expr_contexts(_, 3, e) and not augstore(_, this))
|
||||
exists(Expr e | e = toAst(this) | py_expr_contexts(_, 3, e) and not augstore(_, this))
|
||||
}
|
||||
|
||||
/** Whether this control flow node is a store (including those in augmented assignments) */
|
||||
predicate isStore() {
|
||||
exists(Py::Expr e | e = toAst(this) | py_expr_contexts(_, 5, e) or augstore(_, this))
|
||||
exists(Expr e | e = toAst(this) | py_expr_contexts(_, 5, e) or augstore(_, this))
|
||||
}
|
||||
|
||||
/** Whether this control flow node is a delete */
|
||||
predicate isDelete() { exists(Py::Expr e | e = toAst(this) | py_expr_contexts(_, 2, e)) }
|
||||
predicate isDelete() { exists(Expr e | e = toAst(this) | py_expr_contexts(_, 2, e)) }
|
||||
|
||||
/** Whether this control flow node is a parameter */
|
||||
predicate isParameter() { exists(Py::Expr e | e = toAst(this) | py_expr_contexts(_, 4, e)) }
|
||||
predicate isParameter() { exists(Expr e | e = toAst(this) | py_expr_contexts(_, 4, e)) }
|
||||
|
||||
/** Whether this control flow node is a store in an augmented assignment */
|
||||
predicate isAugStore() { augstore(_, this) }
|
||||
@@ -57,61 +57,61 @@ class ControlFlowNode extends @py_flow_node {
|
||||
|
||||
/** Whether this flow node corresponds to a literal */
|
||||
predicate isLiteral() {
|
||||
toAst(this) instanceof Py::Bytes
|
||||
toAst(this) instanceof Bytes
|
||||
or
|
||||
toAst(this) instanceof Py::Dict
|
||||
toAst(this) instanceof Dict
|
||||
or
|
||||
toAst(this) instanceof Py::DictComp
|
||||
toAst(this) instanceof DictComp
|
||||
or
|
||||
toAst(this) instanceof Py::Set
|
||||
toAst(this) instanceof Set
|
||||
or
|
||||
toAst(this) instanceof Py::SetComp
|
||||
toAst(this) instanceof SetComp
|
||||
or
|
||||
toAst(this) instanceof Py::Ellipsis
|
||||
toAst(this) instanceof Ellipsis
|
||||
or
|
||||
toAst(this) instanceof Py::GeneratorExp
|
||||
toAst(this) instanceof GeneratorExp
|
||||
or
|
||||
toAst(this) instanceof Py::Lambda
|
||||
toAst(this) instanceof Lambda
|
||||
or
|
||||
toAst(this) instanceof Py::ListComp
|
||||
toAst(this) instanceof ListComp
|
||||
or
|
||||
toAst(this) instanceof Py::List
|
||||
toAst(this) instanceof List
|
||||
or
|
||||
toAst(this) instanceof Py::Num
|
||||
toAst(this) instanceof Num
|
||||
or
|
||||
toAst(this) instanceof Py::Tuple
|
||||
toAst(this) instanceof Tuple
|
||||
or
|
||||
toAst(this) instanceof Py::Unicode
|
||||
toAst(this) instanceof Unicode
|
||||
or
|
||||
toAst(this) instanceof Py::NameConstant
|
||||
toAst(this) instanceof NameConstant
|
||||
}
|
||||
|
||||
/** Whether this flow node corresponds to an attribute expression */
|
||||
predicate isAttribute() { toAst(this) instanceof Py::Attribute }
|
||||
predicate isAttribute() { toAst(this) instanceof Attribute }
|
||||
|
||||
/** Whether this flow node corresponds to an subscript expression */
|
||||
predicate isSubscript() { toAst(this) instanceof Py::Subscript }
|
||||
predicate isSubscript() { toAst(this) instanceof Subscript }
|
||||
|
||||
/** Whether this flow node corresponds to an import member */
|
||||
predicate isImportMember() { toAst(this) instanceof Py::ImportMember }
|
||||
predicate isImportMember() { toAst(this) instanceof ImportMember }
|
||||
|
||||
/** Whether this flow node corresponds to a call */
|
||||
predicate isCall() { toAst(this) instanceof Py::Call }
|
||||
predicate isCall() { toAst(this) instanceof Call }
|
||||
|
||||
/** Whether this flow node is the first in a module */
|
||||
predicate isModuleEntry() { this.isEntryNode() and toAst(this) instanceof Py::Module }
|
||||
predicate isModuleEntry() { this.isEntryNode() and toAst(this) instanceof Module }
|
||||
|
||||
/** Whether this flow node corresponds to an import */
|
||||
predicate isImport() { toAst(this) instanceof Py::ImportExpr }
|
||||
predicate isImport() { toAst(this) instanceof ImportExpr }
|
||||
|
||||
/** Whether this flow node corresponds to a conditional expression */
|
||||
predicate isIfExp() { toAst(this) instanceof Py::IfExp }
|
||||
predicate isIfExp() { toAst(this) instanceof IfExp }
|
||||
|
||||
/** Whether this flow node corresponds to a function definition expression */
|
||||
predicate isFunction() { toAst(this) instanceof Py::FunctionExpr }
|
||||
predicate isFunction() { toAst(this) instanceof FunctionExpr }
|
||||
|
||||
/** Whether this flow node corresponds to a class definition expression */
|
||||
predicate isClass() { toAst(this) instanceof Py::ClassExpr }
|
||||
predicate isClass() { toAst(this) instanceof ClassExpr }
|
||||
|
||||
/** Gets a predecessor of this flow node */
|
||||
ControlFlowNode getAPredecessor() { this = result.getASuccessor() }
|
||||
@@ -123,25 +123,25 @@ class ControlFlowNode extends @py_flow_node {
|
||||
ControlFlowNode getImmediateDominator() { py_idoms(this, result) }
|
||||
|
||||
/** Gets the syntactic element corresponding to this flow node */
|
||||
Py::AstNode getNode() { py_flow_bb_node(this, result, _, _) }
|
||||
AstNode getNode() { py_flow_bb_node(this, result, _, _) }
|
||||
|
||||
/** Gets a textual representation of this element. */
|
||||
cached
|
||||
string toString() {
|
||||
Stages::AST::ref() and
|
||||
// Since modules can have ambigous names, entry nodes can too, if we do not collate them.
|
||||
exists(Py::Scope s | s.getEntryNode() = this |
|
||||
exists(Scope s | s.getEntryNode() = this |
|
||||
result = "Entry node for " + concat( | | s.toString(), ",")
|
||||
)
|
||||
or
|
||||
exists(Py::Scope s | s.getANormalExit() = this | result = "Exit node for " + s.toString())
|
||||
exists(Scope s | s.getANormalExit() = this | result = "Exit node for " + s.toString())
|
||||
or
|
||||
not exists(Py::Scope s | s.getEntryNode() = this or s.getANormalExit() = this) and
|
||||
not exists(Scope s | s.getEntryNode() = this or s.getANormalExit() = this) and
|
||||
result = "ControlFlowNode for " + this.getNode().toString()
|
||||
}
|
||||
|
||||
/** Gets the location of this ControlFlowNode */
|
||||
Py::Location getLocation() { result = this.getNode().getLocation() }
|
||||
Location getLocation() { result = this.getNode().getLocation() }
|
||||
|
||||
/** Whether this flow node is the first in its scope */
|
||||
predicate isEntryNode() { py_scope_flow(this, _, -1) }
|
||||
@@ -151,9 +151,9 @@ class ControlFlowNode extends @py_flow_node {
|
||||
|
||||
/** Gets the scope containing this flow node */
|
||||
cached
|
||||
Py::Scope getScope() {
|
||||
Scope getScope() {
|
||||
Stages::AST::ref() and
|
||||
if this.getNode() instanceof Py::Scope
|
||||
if this.getNode() instanceof Scope
|
||||
then
|
||||
/* Entry or exit node */
|
||||
result = this.getNode()
|
||||
@@ -161,7 +161,7 @@ class ControlFlowNode extends @py_flow_node {
|
||||
}
|
||||
|
||||
/** Gets the enclosing module */
|
||||
Py::Module getEnclosingModule() { result = this.getScope().getEnclosingModule() }
|
||||
Module getEnclosingModule() { result = this.getScope().getEnclosingModule() }
|
||||
|
||||
/** Gets a successor for this node if the relevant condition is True. */
|
||||
ControlFlowNode getATrueSuccessor() {
|
||||
@@ -188,7 +188,7 @@ class ControlFlowNode extends @py_flow_node {
|
||||
}
|
||||
|
||||
/** Whether the scope may be exited as a result of this node raising an exception */
|
||||
predicate isExceptionalExit(Py::Scope s) { py_scope_flow(this, s, 1) }
|
||||
predicate isExceptionalExit(Scope s) { py_scope_flow(this, s, 1) }
|
||||
|
||||
/** Whether this node is a normal (non-exceptional) exit */
|
||||
predicate isNormalExit() { py_scope_flow(this, _, 0) or py_scope_flow(this, _, 2) }
|
||||
@@ -236,7 +236,7 @@ class ControlFlowNode extends @py_flow_node {
|
||||
/* join-ordering helper for `getAChild() */
|
||||
pragma[noinline]
|
||||
private ControlFlowNode getExprChild(BasicBlock dom) {
|
||||
this.getNode().(Py::Expr).getAChildNode() = result.getNode() and
|
||||
this.getNode().(Expr).getAChildNode() = result.getNode() and
|
||||
result.getBasicBlock().dominates(dom) and
|
||||
not this instanceof UnaryExprNode
|
||||
}
|
||||
@@ -249,16 +249,16 @@ class ControlFlowNode extends @py_flow_node {
|
||||
*/
|
||||
|
||||
private class AnyNode extends ControlFlowNode {
|
||||
override Py::AstNode getNode() { result = super.getNode() }
|
||||
override AstNode getNode() { result = super.getNode() }
|
||||
}
|
||||
|
||||
/** A control flow node corresponding to a call expression, such as `func(...)` */
|
||||
class CallNode extends ControlFlowNode {
|
||||
CallNode() { toAst(this) instanceof Py::Call }
|
||||
CallNode() { toAst(this) instanceof Call }
|
||||
|
||||
/** Gets the flow node corresponding to the function expression for the call corresponding to this flow node */
|
||||
ControlFlowNode getFunction() {
|
||||
exists(Py::Call c |
|
||||
exists(Call c |
|
||||
this.getNode() = c and
|
||||
c.getFunc() = result.getNode() and
|
||||
result.getBasicBlock().dominates(this.getBasicBlock())
|
||||
@@ -267,7 +267,7 @@ class CallNode extends ControlFlowNode {
|
||||
|
||||
/** Gets the flow node corresponding to the n'th positional argument of the call corresponding to this flow node */
|
||||
ControlFlowNode getArg(int n) {
|
||||
exists(Py::Call c |
|
||||
exists(Call c |
|
||||
this.getNode() = c and
|
||||
c.getArg(n) = result.getNode() and
|
||||
result.getBasicBlock().dominates(this.getBasicBlock())
|
||||
@@ -276,7 +276,7 @@ class CallNode extends ControlFlowNode {
|
||||
|
||||
/** Gets the flow node corresponding to the named argument of the call corresponding to this flow node */
|
||||
ControlFlowNode getArgByName(string name) {
|
||||
exists(Py::Call c, Py::Keyword k |
|
||||
exists(Call c, Keyword k |
|
||||
this.getNode() = c and
|
||||
k = c.getANamedArg() and
|
||||
k.getValue() = result.getNode() and
|
||||
@@ -292,7 +292,7 @@ class CallNode extends ControlFlowNode {
|
||||
result = this.getArgByName(_)
|
||||
}
|
||||
|
||||
override Py::Call getNode() { result = super.getNode() }
|
||||
override Call getNode() { result = super.getNode() }
|
||||
|
||||
predicate isDecoratorCall() {
|
||||
this.isClassDecoratorCall()
|
||||
@@ -301,11 +301,11 @@ class CallNode extends ControlFlowNode {
|
||||
}
|
||||
|
||||
predicate isClassDecoratorCall() {
|
||||
exists(Py::ClassExpr cls | this.getNode() = cls.getADecoratorCall())
|
||||
exists(ClassExpr cls | this.getNode() = cls.getADecoratorCall())
|
||||
}
|
||||
|
||||
predicate isFunctionDecoratorCall() {
|
||||
exists(Py::FunctionExpr func | this.getNode() = func.getADecoratorCall())
|
||||
exists(FunctionExpr func | this.getNode() = func.getADecoratorCall())
|
||||
}
|
||||
|
||||
/** Gets the first tuple (*) argument of this call, if any. */
|
||||
@@ -323,11 +323,11 @@ class CallNode extends ControlFlowNode {
|
||||
|
||||
/** A control flow corresponding to an attribute expression, such as `value.attr` */
|
||||
class AttrNode extends ControlFlowNode {
|
||||
AttrNode() { toAst(this) instanceof Py::Attribute }
|
||||
AttrNode() { toAst(this) instanceof Attribute }
|
||||
|
||||
/** Gets the flow node corresponding to the object of the attribute expression corresponding to this flow node */
|
||||
ControlFlowNode getObject() {
|
||||
exists(Py::Attribute a |
|
||||
exists(Attribute a |
|
||||
this.getNode() = a and
|
||||
a.getObject() = result.getNode() and
|
||||
result.getBasicBlock().dominates(this.getBasicBlock())
|
||||
@@ -339,7 +339,7 @@ class AttrNode extends ControlFlowNode {
|
||||
* with the matching name
|
||||
*/
|
||||
ControlFlowNode getObject(string name) {
|
||||
exists(Py::Attribute a |
|
||||
exists(Attribute a |
|
||||
this.getNode() = a and
|
||||
a.getObject() = result.getNode() and
|
||||
a.getName() = name and
|
||||
@@ -348,57 +348,57 @@ class AttrNode extends ControlFlowNode {
|
||||
}
|
||||
|
||||
/** Gets the attribute name of the attribute expression corresponding to this flow node */
|
||||
string getName() { exists(Py::Attribute a | this.getNode() = a and a.getName() = result) }
|
||||
string getName() { exists(Attribute a | this.getNode() = a and a.getName() = result) }
|
||||
|
||||
override Py::Attribute getNode() { result = super.getNode() }
|
||||
override Attribute getNode() { result = super.getNode() }
|
||||
}
|
||||
|
||||
/** A control flow node corresponding to a `from ... import ...` expression */
|
||||
class ImportMemberNode extends ControlFlowNode {
|
||||
ImportMemberNode() { toAst(this) instanceof Py::ImportMember }
|
||||
ImportMemberNode() { toAst(this) instanceof ImportMember }
|
||||
|
||||
/**
|
||||
* Gets the flow node corresponding to the module in the import-member expression corresponding to this flow node,
|
||||
* with the matching name
|
||||
*/
|
||||
ControlFlowNode getModule(string name) {
|
||||
exists(Py::ImportMember i | this.getNode() = i and i.getModule() = result.getNode() |
|
||||
exists(ImportMember i | this.getNode() = i and i.getModule() = result.getNode() |
|
||||
i.getName() = name and
|
||||
result.getBasicBlock().dominates(this.getBasicBlock())
|
||||
)
|
||||
}
|
||||
|
||||
override Py::ImportMember getNode() { result = super.getNode() }
|
||||
override ImportMember getNode() { result = super.getNode() }
|
||||
}
|
||||
|
||||
/** A control flow node corresponding to an artificial expression representing an import */
|
||||
class ImportExprNode extends ControlFlowNode {
|
||||
ImportExprNode() { toAst(this) instanceof Py::ImportExpr }
|
||||
ImportExprNode() { toAst(this) instanceof ImportExpr }
|
||||
|
||||
override Py::ImportExpr getNode() { result = super.getNode() }
|
||||
override ImportExpr getNode() { result = super.getNode() }
|
||||
}
|
||||
|
||||
/** A control flow node corresponding to a `from ... import *` statement */
|
||||
class ImportStarNode extends ControlFlowNode {
|
||||
ImportStarNode() { toAst(this) instanceof Py::ImportStar }
|
||||
ImportStarNode() { toAst(this) instanceof ImportStar }
|
||||
|
||||
/** Gets the flow node corresponding to the module in the import-star corresponding to this flow node */
|
||||
ControlFlowNode getModule() {
|
||||
exists(Py::ImportStar i | this.getNode() = i and i.getModuleExpr() = result.getNode() |
|
||||
exists(ImportStar i | this.getNode() = i and i.getModuleExpr() = result.getNode() |
|
||||
result.getBasicBlock().dominates(this.getBasicBlock())
|
||||
)
|
||||
}
|
||||
|
||||
override Py::ImportStar getNode() { result = super.getNode() }
|
||||
override ImportStar getNode() { result = super.getNode() }
|
||||
}
|
||||
|
||||
/** A control flow node corresponding to a subscript expression, such as `value[slice]` */
|
||||
class SubscriptNode extends ControlFlowNode {
|
||||
SubscriptNode() { toAst(this) instanceof Py::Subscript }
|
||||
SubscriptNode() { toAst(this) instanceof Subscript }
|
||||
|
||||
/** flow node corresponding to the value of the sequence in a subscript operation */
|
||||
ControlFlowNode getObject() {
|
||||
exists(Py::Subscript s |
|
||||
exists(Subscript s |
|
||||
this.getNode() = s and
|
||||
s.getObject() = result.getNode() and
|
||||
result.getBasicBlock().dominates(this.getBasicBlock())
|
||||
@@ -407,23 +407,23 @@ class SubscriptNode extends ControlFlowNode {
|
||||
|
||||
/** flow node corresponding to the index in a subscript operation */
|
||||
ControlFlowNode getIndex() {
|
||||
exists(Py::Subscript s |
|
||||
exists(Subscript s |
|
||||
this.getNode() = s and
|
||||
s.getIndex() = result.getNode() and
|
||||
result.getBasicBlock().dominates(this.getBasicBlock())
|
||||
)
|
||||
}
|
||||
|
||||
override Py::Subscript getNode() { result = super.getNode() }
|
||||
override Subscript getNode() { result = super.getNode() }
|
||||
}
|
||||
|
||||
/** A control flow node corresponding to a comparison operation, such as `x<y` */
|
||||
class CompareNode extends ControlFlowNode {
|
||||
CompareNode() { toAst(this) instanceof Py::Compare }
|
||||
CompareNode() { toAst(this) instanceof Compare }
|
||||
|
||||
/** Whether left and right are a pair of operands for this comparison */
|
||||
predicate operands(ControlFlowNode left, Py::Cmpop op, ControlFlowNode right) {
|
||||
exists(Py::Compare c, Py::Expr eleft, Py::Expr eright |
|
||||
predicate operands(ControlFlowNode left, Cmpop op, ControlFlowNode right) {
|
||||
exists(Compare c, Expr eleft, Expr eright |
|
||||
this.getNode() = c and left.getNode() = eleft and right.getNode() = eright
|
||||
|
|
||||
eleft = c.getLeft() and eright = c.getComparator(0) and op = c.getOp(0)
|
||||
@@ -436,26 +436,26 @@ class CompareNode extends ControlFlowNode {
|
||||
right.getBasicBlock().dominates(this.getBasicBlock())
|
||||
}
|
||||
|
||||
override Py::Compare getNode() { result = super.getNode() }
|
||||
override Compare getNode() { result = super.getNode() }
|
||||
}
|
||||
|
||||
/** A control flow node corresponding to a conditional expression such as, `body if test else orelse` */
|
||||
class IfExprNode extends ControlFlowNode {
|
||||
IfExprNode() { toAst(this) instanceof Py::IfExp }
|
||||
IfExprNode() { toAst(this) instanceof IfExp }
|
||||
|
||||
/** flow node corresponding to one of the operands of an if-expression */
|
||||
ControlFlowNode getAnOperand() { result = this.getAPredecessor() }
|
||||
|
||||
override Py::IfExp getNode() { result = super.getNode() }
|
||||
override IfExp getNode() { result = super.getNode() }
|
||||
}
|
||||
|
||||
/** A control flow node corresponding to an assignment expression such as `lhs := rhs`. */
|
||||
class AssignmentExprNode extends ControlFlowNode {
|
||||
AssignmentExprNode() { toAst(this) instanceof Py::AssignExpr }
|
||||
AssignmentExprNode() { toAst(this) instanceof AssignExpr }
|
||||
|
||||
/** Gets the flow node corresponding to the left-hand side of the assignment expression */
|
||||
ControlFlowNode getTarget() {
|
||||
exists(Py::AssignExpr a |
|
||||
exists(AssignExpr a |
|
||||
this.getNode() = a and
|
||||
a.getTarget() = result.getNode() and
|
||||
result.getBasicBlock().dominates(this.getBasicBlock())
|
||||
@@ -464,27 +464,27 @@ class AssignmentExprNode extends ControlFlowNode {
|
||||
|
||||
/** Gets the flow node corresponding to the right-hand side of the assignment expression */
|
||||
ControlFlowNode getValue() {
|
||||
exists(Py::AssignExpr a |
|
||||
exists(AssignExpr a |
|
||||
this.getNode() = a and
|
||||
a.getValue() = result.getNode() and
|
||||
result.getBasicBlock().dominates(this.getBasicBlock())
|
||||
)
|
||||
}
|
||||
|
||||
override Py::AssignExpr getNode() { result = super.getNode() }
|
||||
override AssignExpr getNode() { result = super.getNode() }
|
||||
}
|
||||
|
||||
/** A control flow node corresponding to a binary expression, such as `x + y` */
|
||||
class BinaryExprNode extends ControlFlowNode {
|
||||
BinaryExprNode() { toAst(this) instanceof Py::BinaryExpr }
|
||||
BinaryExprNode() { toAst(this) instanceof BinaryExpr }
|
||||
|
||||
/** flow node corresponding to one of the operands of a binary expression */
|
||||
ControlFlowNode getAnOperand() { result = this.getLeft() or result = this.getRight() }
|
||||
|
||||
override Py::BinaryExpr getNode() { result = super.getNode() }
|
||||
override BinaryExpr getNode() { result = super.getNode() }
|
||||
|
||||
ControlFlowNode getLeft() {
|
||||
exists(Py::BinaryExpr b |
|
||||
exists(BinaryExpr b |
|
||||
this.getNode() = b and
|
||||
result.getNode() = b.getLeft() and
|
||||
result.getBasicBlock().dominates(this.getBasicBlock())
|
||||
@@ -492,7 +492,7 @@ class BinaryExprNode extends ControlFlowNode {
|
||||
}
|
||||
|
||||
ControlFlowNode getRight() {
|
||||
exists(Py::BinaryExpr b |
|
||||
exists(BinaryExpr b |
|
||||
this.getNode() = b and
|
||||
result.getNode() = b.getRight() and
|
||||
result.getBasicBlock().dominates(this.getBasicBlock())
|
||||
@@ -500,11 +500,11 @@ class BinaryExprNode extends ControlFlowNode {
|
||||
}
|
||||
|
||||
/** Gets the operator of this binary expression node. */
|
||||
Py::Operator getOp() { result = this.getNode().getOp() }
|
||||
Operator getOp() { result = this.getNode().getOp() }
|
||||
|
||||
/** Whether left and right are a pair of operands for this binary expression */
|
||||
predicate operands(ControlFlowNode left, Py::Operator op, ControlFlowNode right) {
|
||||
exists(Py::BinaryExpr b, Py::Expr eleft, Py::Expr eright |
|
||||
predicate operands(ControlFlowNode left, Operator op, ControlFlowNode right) {
|
||||
exists(BinaryExpr b, Expr eleft, Expr eright |
|
||||
this.getNode() = b and left.getNode() = eleft and right.getNode() = eright
|
||||
|
|
||||
eleft = b.getLeft() and eright = b.getRight() and op = b.getOp()
|
||||
@@ -516,20 +516,20 @@ class BinaryExprNode extends ControlFlowNode {
|
||||
|
||||
/** A control flow node corresponding to a boolean shortcut (and/or) operation */
|
||||
class BoolExprNode extends ControlFlowNode {
|
||||
BoolExprNode() { toAst(this) instanceof Py::BoolExpr }
|
||||
BoolExprNode() { toAst(this) instanceof BoolExpr }
|
||||
|
||||
/** flow node corresponding to one of the operands of a boolean expression */
|
||||
ControlFlowNode getAnOperand() {
|
||||
exists(Py::BoolExpr b | this.getNode() = b and result.getNode() = b.getAValue()) and
|
||||
exists(BoolExpr b | this.getNode() = b and result.getNode() = b.getAValue()) and
|
||||
this.getBasicBlock().dominates(result.getBasicBlock())
|
||||
}
|
||||
|
||||
override Py::BoolExpr getNode() { result = super.getNode() }
|
||||
override BoolExpr getNode() { result = super.getNode() }
|
||||
}
|
||||
|
||||
/** A control flow node corresponding to a unary expression: (`+x`), (`-x`) or (`~x`) */
|
||||
class UnaryExprNode extends ControlFlowNode {
|
||||
UnaryExprNode() { toAst(this) instanceof Py::UnaryExpr }
|
||||
UnaryExprNode() { toAst(this) instanceof UnaryExpr }
|
||||
|
||||
/**
|
||||
* Gets flow node corresponding to the operand of a unary expression.
|
||||
@@ -540,7 +540,7 @@ class UnaryExprNode extends ControlFlowNode {
|
||||
*/
|
||||
ControlFlowNode getOperand() { result = this.getAPredecessor() }
|
||||
|
||||
override Py::UnaryExpr getNode() { result = super.getNode() }
|
||||
override UnaryExpr getNode() { result = super.getNode() }
|
||||
|
||||
override ControlFlowNode getAChild() { result = this.getAPredecessor() }
|
||||
}
|
||||
@@ -555,27 +555,27 @@ class DefinitionNode extends ControlFlowNode {
|
||||
cached
|
||||
DefinitionNode() {
|
||||
Stages::AST::ref() and
|
||||
exists(Py::Assign a | this.getNode() = a.getATarget())
|
||||
exists(Assign a | a.getATarget().getAFlowNode() = this)
|
||||
or
|
||||
exists(Py::AssignExpr a | this.getNode() = a.getTarget())
|
||||
exists(AssignExpr a | a.getTarget().getAFlowNode() = this)
|
||||
or
|
||||
exists(Py::AnnAssign a | this.getNode() = a.getTarget() and exists(a.getValue()))
|
||||
exists(AnnAssign a | a.getTarget().getAFlowNode() = this and exists(a.getValue()))
|
||||
or
|
||||
exists(Py::Alias a | this.getNode() = a.getAsname())
|
||||
exists(Alias a | a.getAsname().getAFlowNode() = this)
|
||||
or
|
||||
augstore(_, this)
|
||||
or
|
||||
// `x, y = 1, 2` where LHS is a combination of list or tuples
|
||||
exists(Py::Assign a | this.getNode() = list_or_tuple_nested_element(a.getATarget()))
|
||||
exists(Assign a | list_or_tuple_nested_element(a.getATarget()).getAFlowNode() = this)
|
||||
or
|
||||
exists(Py::For for | this.getNode() = for.getTarget())
|
||||
exists(For for | for.getTarget().getAFlowNode() = this)
|
||||
or
|
||||
exists(Py::Parameter param | this.getNode() = param.asName() and exists(param.getDefault()))
|
||||
exists(Parameter param | this = param.asName().getAFlowNode() and exists(param.getDefault()))
|
||||
}
|
||||
|
||||
/** flow node corresponding to the value assigned for the definition corresponding to this flow node */
|
||||
ControlFlowNode getValue() {
|
||||
result.getNode() = assigned_value(this.getNode()) and
|
||||
result = assigned_value(this.getNode()).getAFlowNode() and
|
||||
(
|
||||
result.getBasicBlock().dominates(this.getBasicBlock())
|
||||
or
|
||||
@@ -584,16 +584,16 @@ class DefinitionNode extends ControlFlowNode {
|
||||
// since the default value for a parameter is evaluated in the same basic block as
|
||||
// the function definition, but the parameter belongs to the basic block of the function,
|
||||
// there is no dominance relationship between the two.
|
||||
exists(Py::Parameter param | this.getNode() = param.asName())
|
||||
exists(Parameter param | this = param.asName().getAFlowNode())
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private Py::Expr list_or_tuple_nested_element(Py::Expr list_or_tuple) {
|
||||
exists(Py::Expr elt |
|
||||
elt = list_or_tuple.(Py::Tuple).getAnElt()
|
||||
private Expr list_or_tuple_nested_element(Expr list_or_tuple) {
|
||||
exists(Expr elt |
|
||||
elt = list_or_tuple.(Tuple).getAnElt()
|
||||
or
|
||||
elt = list_or_tuple.(Py::List).getAnElt()
|
||||
elt = list_or_tuple.(List).getAnElt()
|
||||
|
|
||||
result = elt
|
||||
or
|
||||
@@ -603,12 +603,12 @@ private Py::Expr list_or_tuple_nested_element(Py::Expr list_or_tuple) {
|
||||
|
||||
/**
|
||||
* A control flow node corresponding to a deletion statement, such as `del x`.
|
||||
* There can be multiple `DeletionNode`s for each `Py::Delete` such that each
|
||||
* There can be multiple `DeletionNode`s for each `Delete` such that each
|
||||
* target has own `DeletionNode`. The CFG for `del a, x.y` looks like:
|
||||
* `NameNode('a') -> DeletionNode -> NameNode('b') -> AttrNode('y') -> DeletionNode`.
|
||||
*/
|
||||
class DeletionNode extends ControlFlowNode {
|
||||
DeletionNode() { toAst(this) instanceof Py::Delete }
|
||||
DeletionNode() { toAst(this) instanceof Delete }
|
||||
|
||||
/** Gets the unique target of this deletion node. */
|
||||
ControlFlowNode getTarget() { result.getASuccessor() = this }
|
||||
@@ -617,9 +617,9 @@ class DeletionNode extends ControlFlowNode {
|
||||
/** A control flow node corresponding to a sequence (tuple or list) literal */
|
||||
abstract class SequenceNode extends ControlFlowNode {
|
||||
SequenceNode() {
|
||||
toAst(this) instanceof Py::Tuple
|
||||
toAst(this) instanceof Tuple
|
||||
or
|
||||
toAst(this) instanceof Py::List
|
||||
toAst(this) instanceof List
|
||||
}
|
||||
|
||||
/** Gets the control flow node for an element of this sequence */
|
||||
@@ -632,11 +632,11 @@ abstract class SequenceNode extends ControlFlowNode {
|
||||
|
||||
/** A control flow node corresponding to a tuple expression such as `( 1, 3, 5, 7, 9 )` */
|
||||
class TupleNode extends SequenceNode {
|
||||
TupleNode() { toAst(this) instanceof Py::Tuple }
|
||||
TupleNode() { toAst(this) instanceof Tuple }
|
||||
|
||||
override ControlFlowNode getElement(int n) {
|
||||
Stages::AST::ref() and
|
||||
exists(Py::Tuple t | this.getNode() = t and result.getNode() = t.getElt(n)) and
|
||||
exists(Tuple t | this.getNode() = t and result.getNode() = t.getElt(n)) and
|
||||
(
|
||||
result.getBasicBlock().dominates(this.getBasicBlock())
|
||||
or
|
||||
@@ -647,10 +647,10 @@ class TupleNode extends SequenceNode {
|
||||
|
||||
/** A control flow node corresponding to a list expression, such as `[ 1, 3, 5, 7, 9 ]` */
|
||||
class ListNode extends SequenceNode {
|
||||
ListNode() { toAst(this) instanceof Py::List }
|
||||
ListNode() { toAst(this) instanceof List }
|
||||
|
||||
override ControlFlowNode getElement(int n) {
|
||||
exists(Py::List l | this.getNode() = l and result.getNode() = l.getElt(n)) and
|
||||
exists(List l | this.getNode() = l and result.getNode() = l.getElt(n)) and
|
||||
(
|
||||
result.getBasicBlock().dominates(this.getBasicBlock())
|
||||
or
|
||||
@@ -661,10 +661,10 @@ class ListNode extends SequenceNode {
|
||||
|
||||
/** A control flow node corresponding to a set expression, such as `{ 1, 3, 5, 7, 9 }` */
|
||||
class SetNode extends ControlFlowNode {
|
||||
SetNode() { toAst(this) instanceof Py::Set }
|
||||
SetNode() { toAst(this) instanceof Set }
|
||||
|
||||
ControlFlowNode getAnElement() {
|
||||
exists(Py::Set s | this.getNode() = s and result.getNode() = s.getElt(_)) and
|
||||
exists(Set s | this.getNode() = s and result.getNode() = s.getElt(_)) and
|
||||
(
|
||||
result.getBasicBlock().dominates(this.getBasicBlock())
|
||||
or
|
||||
@@ -675,20 +675,20 @@ class SetNode extends ControlFlowNode {
|
||||
|
||||
/** A control flow node corresponding to a dictionary literal, such as `{ 'a': 1, 'b': 2 }` */
|
||||
class DictNode extends ControlFlowNode {
|
||||
DictNode() { toAst(this) instanceof Py::Dict }
|
||||
DictNode() { toAst(this) instanceof Dict }
|
||||
|
||||
/**
|
||||
* Gets a key of this dictionary literal node, for those items that have keys
|
||||
* E.g, in {'a':1, **b} this returns only 'a'
|
||||
*/
|
||||
ControlFlowNode getAKey() {
|
||||
exists(Py::Dict d | this.getNode() = d and result.getNode() = d.getAKey()) and
|
||||
exists(Dict d | this.getNode() = d and result.getNode() = d.getAKey()) and
|
||||
result.getBasicBlock().dominates(this.getBasicBlock())
|
||||
}
|
||||
|
||||
/** Gets a value of this dictionary literal node */
|
||||
ControlFlowNode getAValue() {
|
||||
exists(Py::Dict d | this.getNode() = d and result.getNode() = d.getAValue()) and
|
||||
exists(Dict d | this.getNode() = d and result.getNode() = d.getAValue()) and
|
||||
result.getBasicBlock().dominates(this.getBasicBlock())
|
||||
}
|
||||
}
|
||||
@@ -712,23 +712,21 @@ class IterableNode extends ControlFlowNode {
|
||||
}
|
||||
}
|
||||
|
||||
private Py::AstNode assigned_value(Py::Expr lhs) {
|
||||
private AstNode assigned_value(Expr lhs) {
|
||||
/* lhs = result */
|
||||
exists(Py::Assign a | a.getATarget() = lhs and result = a.getValue())
|
||||
exists(Assign a | a.getATarget() = lhs and result = a.getValue())
|
||||
or
|
||||
/* lhs := result */
|
||||
exists(Py::AssignExpr a | a.getTarget() = lhs and result = a.getValue())
|
||||
exists(AssignExpr a | a.getTarget() = lhs and result = a.getValue())
|
||||
or
|
||||
/* lhs : annotation = result */
|
||||
exists(Py::AnnAssign a | a.getTarget() = lhs and result = a.getValue())
|
||||
exists(AnnAssign a | a.getTarget() = lhs and result = a.getValue())
|
||||
or
|
||||
/* import result as lhs */
|
||||
exists(Py::Alias a | a.getAsname() = lhs and result = a.getValue())
|
||||
exists(Alias a | a.getAsname() = lhs and result = a.getValue())
|
||||
or
|
||||
/* lhs += x => result = (lhs + x) */
|
||||
exists(Py::AugAssign a, Py::BinaryExpr b |
|
||||
b = a.getOperation() and result = b and lhs = b.getLeft()
|
||||
)
|
||||
exists(AugAssign a, BinaryExpr b | b = a.getOperation() and result = b and lhs = b.getLeft())
|
||||
or
|
||||
/*
|
||||
* ..., lhs, ... = ..., result, ...
|
||||
@@ -736,31 +734,31 @@ private Py::AstNode assigned_value(Py::Expr lhs) {
|
||||
* ..., (..., lhs, ...), ... = ..., (..., result, ...), ...
|
||||
*/
|
||||
|
||||
exists(Py::Assign a | nested_sequence_assign(a.getATarget(), a.getValue(), lhs, result))
|
||||
exists(Assign a | nested_sequence_assign(a.getATarget(), a.getValue(), lhs, result))
|
||||
or
|
||||
/* for lhs in seq: => `result` is the `for` node, representing the `iter(next(seq))` operation. */
|
||||
result.(Py::For).getTarget() = lhs
|
||||
result.(For).getTarget() = lhs
|
||||
or
|
||||
exists(Py::Parameter param | lhs = param.asName() and result = param.getDefault())
|
||||
exists(Parameter param | lhs = param.asName() and result = param.getDefault())
|
||||
}
|
||||
|
||||
predicate nested_sequence_assign(
|
||||
Py::Expr left_parent, Py::Expr right_parent, Py::Expr left_result, Py::Expr right_result
|
||||
Expr left_parent, Expr right_parent, Expr left_result, Expr right_result
|
||||
) {
|
||||
exists(Py::Assign a |
|
||||
exists(Assign a |
|
||||
a.getATarget().getASubExpression*() = left_parent and
|
||||
a.getValue().getASubExpression*() = right_parent
|
||||
) and
|
||||
exists(int i, Py::Expr left_elem, Py::Expr right_elem |
|
||||
exists(int i, Expr left_elem, Expr right_elem |
|
||||
(
|
||||
left_elem = left_parent.(Py::Tuple).getElt(i)
|
||||
left_elem = left_parent.(Tuple).getElt(i)
|
||||
or
|
||||
left_elem = left_parent.(Py::List).getElt(i)
|
||||
left_elem = left_parent.(List).getElt(i)
|
||||
) and
|
||||
(
|
||||
right_elem = right_parent.(Py::Tuple).getElt(i)
|
||||
right_elem = right_parent.(Tuple).getElt(i)
|
||||
or
|
||||
right_elem = right_parent.(Py::List).getElt(i)
|
||||
right_elem = right_parent.(List).getElt(i)
|
||||
)
|
||||
|
|
||||
left_result = left_elem and right_result = right_elem
|
||||
@@ -771,9 +769,9 @@ predicate nested_sequence_assign(
|
||||
|
||||
/** A flow node for a `for` statement. */
|
||||
class ForNode extends ControlFlowNode {
|
||||
ForNode() { toAst(this) instanceof Py::For }
|
||||
ForNode() { toAst(this) instanceof For }
|
||||
|
||||
override Py::For getNode() { result = super.getNode() }
|
||||
override For getNode() { result = super.getNode() }
|
||||
|
||||
/** Holds if this `for` statement causes iteration over `sequence` storing each step of the iteration in `target` */
|
||||
predicate iterates(ControlFlowNode target, ControlFlowNode sequence) {
|
||||
@@ -784,7 +782,7 @@ class ForNode extends ControlFlowNode {
|
||||
|
||||
/** Gets the sequence node for this `for` statement. */
|
||||
ControlFlowNode getSequence() {
|
||||
exists(Py::For for |
|
||||
exists(For for |
|
||||
toAst(this) = for and
|
||||
for.getIter() = result.getNode()
|
||||
|
|
||||
@@ -794,7 +792,7 @@ class ForNode extends ControlFlowNode {
|
||||
|
||||
/** A possible `target` for this `for` statement, not accounting for loop unrolling */
|
||||
private ControlFlowNode possibleTarget() {
|
||||
exists(Py::For for |
|
||||
exists(For for |
|
||||
toAst(this) = for and
|
||||
for.getTarget() = result.getNode() and
|
||||
this.getBasicBlock().dominates(result.getBasicBlock())
|
||||
@@ -811,11 +809,11 @@ class ForNode extends ControlFlowNode {
|
||||
|
||||
/** A flow node for a `raise` statement */
|
||||
class RaiseStmtNode extends ControlFlowNode {
|
||||
RaiseStmtNode() { toAst(this) instanceof Py::Raise }
|
||||
RaiseStmtNode() { toAst(this) instanceof Raise }
|
||||
|
||||
/** Gets the control flow node for the exception raised by this raise statement */
|
||||
ControlFlowNode getException() {
|
||||
exists(Py::Raise r |
|
||||
exists(Raise r |
|
||||
r = toAst(this) and
|
||||
r.getException() = toAst(result) and
|
||||
result.getBasicBlock().dominates(this.getBasicBlock())
|
||||
@@ -829,36 +827,36 @@ class RaiseStmtNode extends ControlFlowNode {
|
||||
*/
|
||||
class NameNode extends ControlFlowNode {
|
||||
NameNode() {
|
||||
exists(Py::Name n | py_flow_bb_node(this, n, _, _))
|
||||
exists(Name n | py_flow_bb_node(this, n, _, _))
|
||||
or
|
||||
exists(Py::PlaceHolder p | py_flow_bb_node(this, p, _, _))
|
||||
exists(PlaceHolder p | py_flow_bb_node(this, p, _, _))
|
||||
}
|
||||
|
||||
/** Whether this flow node defines the variable `v`. */
|
||||
predicate defines(Py::Variable v) {
|
||||
exists(Py::Name d | this.getNode() = d and d.defines(v)) and
|
||||
predicate defines(Variable v) {
|
||||
exists(Name d | this.getNode() = d and d.defines(v)) and
|
||||
not this.isLoad()
|
||||
}
|
||||
|
||||
/** Whether this flow node deletes the variable `v`. */
|
||||
predicate deletes(Py::Variable v) { exists(Py::Name d | this.getNode() = d and d.deletes(v)) }
|
||||
predicate deletes(Variable v) { exists(Name d | this.getNode() = d and d.deletes(v)) }
|
||||
|
||||
/** Whether this flow node uses the variable `v`. */
|
||||
predicate uses(Py::Variable v) {
|
||||
predicate uses(Variable v) {
|
||||
this.isLoad() and
|
||||
exists(Py::Name u | this.getNode() = u and u.uses(v))
|
||||
exists(Name u | this.getNode() = u and u.uses(v))
|
||||
or
|
||||
exists(Py::PlaceHolder u |
|
||||
this.getNode() = u and u.getVariable() = v and u.getCtx() instanceof Py::Load
|
||||
exists(PlaceHolder u |
|
||||
this.getNode() = u and u.getVariable() = v and u.getCtx() instanceof Load
|
||||
)
|
||||
or
|
||||
Scopes::use_of_global_variable(this, v.getScope(), v.getId())
|
||||
}
|
||||
|
||||
string getId() {
|
||||
result = this.getNode().(Py::Name).getId()
|
||||
result = this.getNode().(Name).getId()
|
||||
or
|
||||
result = this.getNode().(Py::PlaceHolder).getId()
|
||||
result = this.getNode().(PlaceHolder).getId()
|
||||
}
|
||||
|
||||
/** Whether this is a use of a local variable. */
|
||||
@@ -870,84 +868,82 @@ class NameNode extends ControlFlowNode {
|
||||
/** Whether this is a use of a global (including builtin) variable. */
|
||||
predicate isGlobal() { Scopes::use_of_global_variable(this, _, _) }
|
||||
|
||||
predicate isSelf() {
|
||||
exists(Py::SsaVariable selfvar | selfvar.isSelf() and selfvar.getAUse() = this)
|
||||
}
|
||||
predicate isSelf() { exists(SsaVariable selfvar | selfvar.isSelf() and selfvar.getAUse() = this) }
|
||||
}
|
||||
|
||||
/** A control flow node corresponding to a named constant, one of `None`, `True` or `False`. */
|
||||
class NameConstantNode extends NameNode {
|
||||
NameConstantNode() { exists(Py::NameConstant n | py_flow_bb_node(this, n, _, _)) }
|
||||
NameConstantNode() { exists(NameConstant n | py_flow_bb_node(this, n, _, _)) }
|
||||
/*
|
||||
* We ought to override uses as well, but that has
|
||||
* a serious performance impact.
|
||||
* deprecated predicate uses(Py::Variable v) { none() }
|
||||
* deprecated predicate uses(Variable v) { none() }
|
||||
*/
|
||||
|
||||
}
|
||||
|
||||
/** A control flow node corresponding to a starred expression, `*a`. */
|
||||
class StarredNode extends ControlFlowNode {
|
||||
StarredNode() { toAst(this) instanceof Py::Starred }
|
||||
StarredNode() { toAst(this) instanceof Starred }
|
||||
|
||||
ControlFlowNode getValue() { toAst(result) = toAst(this).(Py::Starred).getValue() }
|
||||
ControlFlowNode getValue() { toAst(result) = toAst(this).(Starred).getValue() }
|
||||
}
|
||||
|
||||
/** The ControlFlowNode for an 'except' statement. */
|
||||
class ExceptFlowNode extends ControlFlowNode {
|
||||
ExceptFlowNode() { this.getNode() instanceof Py::ExceptStmt }
|
||||
ExceptFlowNode() { this.getNode() instanceof ExceptStmt }
|
||||
|
||||
/**
|
||||
* Gets the type handled by this exception handler.
|
||||
* `Py::ExceptionType` in `except Py::ExceptionType as e:`
|
||||
* `ExceptionType` in `except ExceptionType as e:`
|
||||
*/
|
||||
ControlFlowNode getType() {
|
||||
exists(Py::ExceptStmt ex |
|
||||
exists(ExceptStmt ex |
|
||||
this.getBasicBlock().dominates(result.getBasicBlock()) and
|
||||
ex = this.getNode() and
|
||||
result.getNode() = ex.getType()
|
||||
result = ex.getType().getAFlowNode()
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the name assigned to the handled exception, if any.
|
||||
* `e` in `except Py::ExceptionType as e:`
|
||||
* `e` in `except ExceptionType as e:`
|
||||
*/
|
||||
ControlFlowNode getName() {
|
||||
exists(Py::ExceptStmt ex |
|
||||
exists(ExceptStmt ex |
|
||||
this.getBasicBlock().dominates(result.getBasicBlock()) and
|
||||
ex = this.getNode() and
|
||||
result.getNode() = ex.getName()
|
||||
result = ex.getName().getAFlowNode()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/** The ControlFlowNode for an 'except*' statement. */
|
||||
class ExceptGroupFlowNode extends ControlFlowNode {
|
||||
ExceptGroupFlowNode() { this.getNode() instanceof Py::ExceptGroupStmt }
|
||||
ExceptGroupFlowNode() { this.getNode() instanceof ExceptGroupStmt }
|
||||
|
||||
/**
|
||||
* Gets the type handled by this exception handler.
|
||||
* `Py::ExceptionType` in `except* Py::ExceptionType as e:`
|
||||
* `ExceptionType` in `except* ExceptionType as e:`
|
||||
*/
|
||||
ControlFlowNode getType() {
|
||||
this.getBasicBlock().dominates(result.getBasicBlock()) and
|
||||
result.getNode() = this.getNode().(Py::ExceptGroupStmt).getType()
|
||||
result = this.getNode().(ExceptGroupStmt).getType().getAFlowNode()
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the name assigned to the handled exception, if any.
|
||||
* `e` in `except* Py::ExceptionType as e:`
|
||||
* `e` in `except* ExceptionType as e:`
|
||||
*/
|
||||
ControlFlowNode getName() {
|
||||
this.getBasicBlock().dominates(result.getBasicBlock()) and
|
||||
result.getNode() = this.getNode().(Py::ExceptGroupStmt).getName()
|
||||
result = this.getNode().(ExceptGroupStmt).getName().getAFlowNode()
|
||||
}
|
||||
}
|
||||
|
||||
private module Scopes {
|
||||
private predicate fast_local(NameNode n) {
|
||||
exists(Py::FastLocalVariable v |
|
||||
exists(FastLocalVariable v |
|
||||
n.uses(v) and
|
||||
v.getScope() = n.getScope()
|
||||
)
|
||||
@@ -956,15 +952,15 @@ private module Scopes {
|
||||
predicate local(NameNode n) {
|
||||
fast_local(n)
|
||||
or
|
||||
exists(Py::SsaVariable var |
|
||||
exists(SsaVariable var |
|
||||
var.getAUse() = n and
|
||||
n.getScope() instanceof Py::Class and
|
||||
n.getScope() instanceof Class and
|
||||
exists(var.getDefinition())
|
||||
)
|
||||
}
|
||||
|
||||
predicate non_local(NameNode n) {
|
||||
exists(Py::FastLocalVariable flv |
|
||||
exists(FastLocalVariable flv |
|
||||
flv.getALoad() = n.getNode() and
|
||||
not flv.getScope() = n.getScope()
|
||||
)
|
||||
@@ -972,20 +968,20 @@ private module Scopes {
|
||||
|
||||
// magic is fine, but we get questionable join-ordering of it
|
||||
pragma[nomagic]
|
||||
predicate use_of_global_variable(NameNode n, Py::Module scope, string name) {
|
||||
predicate use_of_global_variable(NameNode n, Module scope, string name) {
|
||||
n.isLoad() and
|
||||
not non_local(n) and
|
||||
not exists(Py::SsaVariable var | var.getAUse() = n |
|
||||
var.getVariable() instanceof Py::FastLocalVariable
|
||||
not exists(SsaVariable var | var.getAUse() = n |
|
||||
var.getVariable() instanceof FastLocalVariable
|
||||
or
|
||||
n.getScope() instanceof Py::Class and
|
||||
n.getScope() instanceof Class and
|
||||
not maybe_undefined(var)
|
||||
) and
|
||||
name = n.getId() and
|
||||
scope = n.getEnclosingModule()
|
||||
}
|
||||
|
||||
private predicate maybe_undefined(Py::SsaVariable var) {
|
||||
private predicate maybe_undefined(SsaVariable var) {
|
||||
not exists(var.getDefinition()) and not py_ssa_phi(var, _)
|
||||
or
|
||||
var.getDefinition().isDelete()
|
||||
@@ -1062,13 +1058,13 @@ class BasicBlock extends @py_flow_node {
|
||||
private predicate oneNodeBlock() { this.firstNode() = this.getLastNode() }
|
||||
|
||||
private predicate startLocationInfo(string file, int line, int col) {
|
||||
if this.firstNode().getNode() instanceof Py::Scope
|
||||
if this.firstNode().getNode() instanceof Scope
|
||||
then this.firstNode().getASuccessor().getLocation().hasLocationInfo(file, line, col, _, _)
|
||||
else this.firstNode().getLocation().hasLocationInfo(file, line, col, _, _)
|
||||
}
|
||||
|
||||
private predicate endLocationInfo(int endl, int endc) {
|
||||
if this.getLastNode().getNode() instanceof Py::Scope and not this.oneNodeBlock()
|
||||
if this.getLastNode().getNode() instanceof Scope and not this.oneNodeBlock()
|
||||
then this.getLastNode().getAPredecessor().getLocation().hasLocationInfo(_, _, _, endl, endc)
|
||||
else this.getLastNode().getLocation().hasLocationInfo(_, _, _, endl, endc)
|
||||
}
|
||||
@@ -1085,7 +1081,7 @@ class BasicBlock extends @py_flow_node {
|
||||
|
||||
/** Whether flow from this basic block reaches a normal exit from its scope */
|
||||
predicate reachesExit() {
|
||||
exists(Py::Scope s | s.getANormalExit().getBasicBlock() = this)
|
||||
exists(Scope s | s.getANormalExit().getBasicBlock() = this)
|
||||
or
|
||||
this.getASuccessor().reachesExit()
|
||||
}
|
||||
@@ -1126,7 +1122,7 @@ class BasicBlock extends @py_flow_node {
|
||||
|
||||
/** Gets the scope of this block */
|
||||
pragma[nomagic]
|
||||
Py::Scope getScope() {
|
||||
Scope getScope() {
|
||||
exists(ControlFlowNode n | n.getBasicBlock() = this |
|
||||
/* Take care not to use an entry or exit node as that node's scope will be the outer scope */
|
||||
not py_scope_flow(n, _, -1) and
|
||||
@@ -1149,17 +1145,17 @@ class BasicBlock extends @py_flow_node {
|
||||
predicate reaches(BasicBlock other) { this = other or this.strictlyReaches(other) }
|
||||
|
||||
/**
|
||||
* Gets the `Py::ConditionBlock`, if any, that controls this block and
|
||||
* does not control any other `Py::ConditionBlock`s that control this block.
|
||||
* That is the `Py::ConditionBlock` that is closest dominator.
|
||||
* Gets the `ConditionBlock`, if any, that controls this block and
|
||||
* does not control any other `ConditionBlock`s that control this block.
|
||||
* That is the `ConditionBlock` that is closest dominator.
|
||||
*/
|
||||
Py::ConditionBlock getImmediatelyControllingBlock() {
|
||||
ConditionBlock getImmediatelyControllingBlock() {
|
||||
result = this.nonControllingImmediateDominator*().getImmediateDominator()
|
||||
}
|
||||
|
||||
private BasicBlock nonControllingImmediateDominator() {
|
||||
result = this.getImmediateDominator() and
|
||||
not result.(Py::ConditionBlock).controls(this, _)
|
||||
not result.(ConditionBlock).controls(this, _)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1179,7 +1175,7 @@ private class ControlFlowNodeAlias = ControlFlowNode;
|
||||
|
||||
final private class FinalBasicBlock = BasicBlock;
|
||||
|
||||
module Cfg implements BB::CfgSig<Py::Location> {
|
||||
module Cfg implements BB::CfgSig<Location> {
|
||||
private import codeql.controlflow.SuccessorType
|
||||
|
||||
class ControlFlowNode = ControlFlowNodeAlias;
|
||||
@@ -1190,7 +1186,7 @@ module Cfg implements BB::CfgSig<Py::Location> {
|
||||
// Using the location of the first node is simple
|
||||
// and we just need a way to identify the basic block
|
||||
// during debugging, so this will be serviceable.
|
||||
Py::Location getLocation() { result = super.getNode(0).getLocation() }
|
||||
Location getLocation() { result = super.getNode(0).getLocation() }
|
||||
|
||||
int length() { result = count(int i | exists(this.getNode(i))) }
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@ overlay[local]
|
||||
module;
|
||||
|
||||
import python
|
||||
private import semmle.python.controlflow.internal.Cfg as Cfg
|
||||
|
||||
/**
|
||||
* A function, independent of defaults and binding.
|
||||
@@ -154,16 +153,8 @@ class Function extends Function_, Scope, AstNode {
|
||||
|
||||
override predicate contains(AstNode inner) { Scope.super.contains(inner) }
|
||||
|
||||
/**
|
||||
* DEPRECATED: bind a `Return` node explicitly instead, e.g.
|
||||
* `exists(Return ret | ret.getScope() = this and n.getNode() = ret.getValue())`.
|
||||
* This API is being phased out together with `AstNode.getAFlowNode()` to
|
||||
* untangle the AST and CFG hierarchies.
|
||||
*
|
||||
* Gets a control flow node for a return value of this function, from the
|
||||
* new (shared) CFG.
|
||||
*/
|
||||
deprecated Cfg::ControlFlowNode getAReturnValueFlowNode() {
|
||||
/** Gets a control flow node for a return value of this function */
|
||||
ControlFlowNode getAReturnValueFlowNode() {
|
||||
exists(Return ret |
|
||||
ret.getScope() = this and
|
||||
ret.getValue() = result.getNode()
|
||||
|
||||
@@ -162,6 +162,8 @@ class ImportMember extends ImportMember_ {
|
||||
string getImportedModuleName() {
|
||||
result = this.getModule().(ImportExpr).getImportedModuleName() + "." + this.getName()
|
||||
}
|
||||
|
||||
override ImportMemberNode getAFlowNode() { result = super.getAFlowNode() }
|
||||
}
|
||||
|
||||
/** An import statement */
|
||||
|
||||
@@ -46,23 +46,20 @@ class SelfAttributeRead extends SelfAttribute {
|
||||
}
|
||||
|
||||
predicate guardedByHasattr() {
|
||||
exists(Variable var, ControlFlowNode n, ControlFlowNode this_, ControlFlowNode obj_ |
|
||||
this_.getNode() = this and obj_.getNode() = this.getObject()
|
||||
|
|
||||
var.getAUse() = obj_ and
|
||||
exists(Variable var, ControlFlowNode n |
|
||||
var.getAUse() = this.getObject().getAFlowNode() and
|
||||
hasattr(n, var.getAUse(), this.getName()) and
|
||||
n.strictlyDominates(this_)
|
||||
n.strictlyDominates(this.getAFlowNode())
|
||||
)
|
||||
}
|
||||
|
||||
pragma[noinline]
|
||||
predicate locallyDefined() {
|
||||
exists(SelfAttributeStore store, ControlFlowNode store_, ControlFlowNode this_ |
|
||||
store_.getNode() = store and this_.getNode() = this
|
||||
|
|
||||
exists(SelfAttributeStore store |
|
||||
this.getName() = store.getName() and
|
||||
this.getScope() = store.getScope() and
|
||||
store_.strictlyDominates(this_)
|
||||
this.getScope() = store.getScope()
|
||||
|
|
||||
store.getAFlowNode().strictlyDominates(this.getAFlowNode())
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,43 +1,36 @@
|
||||
/** Provides commonly used BarrierGuards. */
|
||||
|
||||
private import python
|
||||
private import semmle.python.controlflow.internal.Cfg as Cfg
|
||||
private import semmle.python.dataflow.new.DataFlow
|
||||
|
||||
private predicate constCompare(DataFlow::GuardNode g, Cfg::ControlFlowNode node, boolean branch) {
|
||||
exists(Cfg::CompareNode cn | cn = g |
|
||||
exists(ImmutableLiteral const, Cmpop op, Cfg::ControlFlowNode c |
|
||||
c.getNode() = const and
|
||||
(
|
||||
op = any(Eq eq) and branch = true
|
||||
or
|
||||
op = any(NotEq ne) and branch = false
|
||||
)
|
||||
|
|
||||
cn.operands(c, op, node)
|
||||
private predicate constCompare(DataFlow::GuardNode g, ControlFlowNode node, boolean branch) {
|
||||
exists(CompareNode cn | cn = g |
|
||||
exists(ImmutableLiteral const, Cmpop op |
|
||||
op = any(Eq eq) and branch = true
|
||||
or
|
||||
cn.operands(node, op, c)
|
||||
op = any(NotEq ne) and branch = false
|
||||
|
|
||||
cn.operands(const.getAFlowNode(), op, node)
|
||||
or
|
||||
cn.operands(node, op, const.getAFlowNode())
|
||||
)
|
||||
or
|
||||
exists(NameConstant const, Cmpop op, Cfg::ControlFlowNode c |
|
||||
c.getNode() = const and
|
||||
(
|
||||
op = any(Is is_) and branch = true
|
||||
or
|
||||
op = any(IsNot isn) and branch = false
|
||||
)
|
||||
|
|
||||
cn.operands(c, op, node)
|
||||
exists(NameConstant const, Cmpop op |
|
||||
op = any(Is is_) and branch = true
|
||||
or
|
||||
cn.operands(node, op, c)
|
||||
op = any(IsNot isn) and branch = false
|
||||
|
|
||||
cn.operands(const.getAFlowNode(), op, node)
|
||||
or
|
||||
cn.operands(node, op, const.getAFlowNode())
|
||||
)
|
||||
or
|
||||
exists(Cfg::IterableNode const_iterable, Cmpop op |
|
||||
exists(IterableNode const_iterable, Cmpop op |
|
||||
op = any(In in_) and branch = true
|
||||
or
|
||||
op = any(NotIn ni) and branch = false
|
||||
|
|
||||
forall(Cfg::ControlFlowNode elem | elem = const_iterable.getAnElement() |
|
||||
forall(ControlFlowNode elem | elem = const_iterable.getAnElement() |
|
||||
elem.getNode() instanceof ImmutableLiteral
|
||||
) and
|
||||
cn.operands(node, op, const_iterable)
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
*/
|
||||
|
||||
private import python
|
||||
private import semmle.python.controlflow.internal.Cfg as Cfg
|
||||
private import semmle.python.dataflow.new.DataFlow
|
||||
// Need to import `semmle.python.Frameworks` since frameworks can extend `SensitiveDataSource::Range`
|
||||
private import semmle.python.Frameworks
|
||||
@@ -106,7 +105,7 @@ private module SensitiveDataModeling {
|
||||
or
|
||||
// to cover functions that we don't have the definition for, and where the
|
||||
// reference to the function has not already been marked as being sensitive
|
||||
this.getFunction().asCfgNode().(Cfg::NameNode).getId() = sensitiveString(classification)
|
||||
this.getFunction().asCfgNode().(NameNode).getId() = sensitiveString(classification)
|
||||
}
|
||||
|
||||
override SensitiveDataClassification getClassification() { result = classification }
|
||||
@@ -252,12 +251,12 @@ private module SensitiveDataModeling {
|
||||
SensitiveDataClassification classification;
|
||||
|
||||
SensitiveVariableAssignment() {
|
||||
exists(Cfg::DefinitionNode def |
|
||||
def.(Cfg::NameNode).getId() = sensitiveString(classification) and
|
||||
exists(DefinitionNode def |
|
||||
def.(NameNode).getId() = sensitiveString(classification) and
|
||||
(
|
||||
this.asCfgNode() = def.getValue()
|
||||
or
|
||||
this.asCfgNode() = def.getValue().(Cfg::ForNode).getSequence()
|
||||
this.asCfgNode() = def.getValue().(ForNode).getSequence()
|
||||
) and
|
||||
not this.asExpr() instanceof FunctionExpr and
|
||||
not this.asExpr() instanceof ClassExpr
|
||||
@@ -294,7 +293,7 @@ private module SensitiveDataModeling {
|
||||
SensitiveDataClassification classification;
|
||||
|
||||
SensitiveSubscript() {
|
||||
this.asCfgNode().(Cfg::SubscriptNode).getIndex() =
|
||||
this.asCfgNode().(SubscriptNode).getIndex() =
|
||||
sensitiveLookupStringConst(classification).asCfgNode()
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@ overlay[local]
|
||||
module;
|
||||
|
||||
private import python
|
||||
private import semmle.python.controlflow.internal.Cfg as Cfg
|
||||
import DataFlowUtil
|
||||
import DataFlowPublic
|
||||
private import DataFlowPrivate
|
||||
@@ -84,9 +83,9 @@ abstract class AttrWrite extends AttrRef {
|
||||
* ```python
|
||||
* object.attr = value
|
||||
* ```
|
||||
* Also gives access to the `value` being written, by extending `Cfg::DefinitionNode`.
|
||||
* Also gives access to the `value` being written, by extending `DefinitionNode`.
|
||||
*/
|
||||
private class AttributeAssignmentNode extends Cfg::DefinitionNode, Cfg::AttrNode { }
|
||||
private class AttributeAssignmentNode extends DefinitionNode, AttrNode { }
|
||||
|
||||
/** A simple attribute assignment: `object.attr = value`. */
|
||||
private class AttributeAssignmentAsAttrWrite extends AttrWrite, CfgNode {
|
||||
@@ -132,13 +131,13 @@ private class GlobalAttributeAssignmentAsAttrWrite extends AttrWrite, CfgNode {
|
||||
override string getAttributeName() { result = node.getName() }
|
||||
}
|
||||
|
||||
/** Represents `Cfg::CallNode`s that may refer to calls to built-in functions or classes. */
|
||||
private class BuiltInCallNode extends Cfg::CallNode {
|
||||
/** Represents `CallNode`s that may refer to calls to built-in functions or classes. */
|
||||
private class BuiltInCallNode extends CallNode {
|
||||
string name;
|
||||
|
||||
BuiltInCallNode() {
|
||||
// TODO disallow instances where the name of the built-in may refer to an in-scope variable of that name.
|
||||
exists(Cfg::NameNode id |
|
||||
exists(NameNode id |
|
||||
name = Builtins::getBuiltinName() and
|
||||
this.getFunction() = id and
|
||||
id.getId() = name and
|
||||
@@ -146,7 +145,7 @@ private class BuiltInCallNode extends Cfg::CallNode {
|
||||
)
|
||||
}
|
||||
|
||||
/** Gets the name of the built-in function that is called at this `Cfg::CallNode` */
|
||||
/** Gets the name of the built-in function that is called at this `CallNode` */
|
||||
string getBuiltinName() { result = name }
|
||||
}
|
||||
|
||||
@@ -158,20 +157,20 @@ private class BuiltinAttrCallNode extends BuiltInCallNode {
|
||||
BuiltinAttrCallNode() { name in ["setattr", "getattr", "hasattr", "delattr"] }
|
||||
|
||||
/** Gets the control flow node for object on which the attribute is accessed. */
|
||||
Cfg::ControlFlowNode getObject() { result in [this.getArg(0), this.getArgByName("object")] }
|
||||
ControlFlowNode getObject() { result in [this.getArg(0), this.getArgByName("object")] }
|
||||
|
||||
/**
|
||||
* Gets the control flow node for the value that is being written to the attribute.
|
||||
* Only relevant for `setattr` calls.
|
||||
*/
|
||||
Cfg::ControlFlowNode getValue() {
|
||||
ControlFlowNode getValue() {
|
||||
// only valid for `setattr`
|
||||
name = "setattr" and
|
||||
result in [this.getArg(2), this.getArgByName("value")]
|
||||
}
|
||||
|
||||
/** Gets the control flow node that defines the name of the attribute being accessed. */
|
||||
Cfg::ControlFlowNode getName() { result in [this.getArg(1), this.getArgByName("name")] }
|
||||
ControlFlowNode getName() { result in [this.getArg(1), this.getArgByName("name")] }
|
||||
}
|
||||
|
||||
/** Represents calls to the built-in `setattr`. */
|
||||
@@ -206,10 +205,10 @@ private class SetAttrCallAsAttrWrite extends AttrWrite, CfgNode {
|
||||
* attr = value
|
||||
* ...
|
||||
* ```
|
||||
* Instances of this class correspond to the `Cfg::NameNode` for `attr`, and also gives access to `value` by
|
||||
* virtue of being a `Cfg::DefinitionNode`.
|
||||
* Instances of this class correspond to the `NameNode` for `attr`, and also gives access to `value` by
|
||||
* virtue of being a `DefinitionNode`.
|
||||
*/
|
||||
private class ClassAttributeAssignmentNode extends Cfg::DefinitionNode, Cfg::NameNode {
|
||||
private class ClassAttributeAssignmentNode extends DefinitionNode, NameNode {
|
||||
ClassAttributeAssignmentNode() { this.getScope() = any(ClassExpr c).getInnerScope() }
|
||||
}
|
||||
|
||||
@@ -229,7 +228,7 @@ private class ClassDefinitionAsAttrWrite extends AttrWrite, CfgNode {
|
||||
|
||||
override Node getValue() { result.asCfgNode() = node.getValue() }
|
||||
|
||||
override Node getObject() { result.asCfgNode().getNode() = cls }
|
||||
override Node getObject() { result.asCfgNode() = cls.getAFlowNode() }
|
||||
|
||||
override ExprNode getAttributeNameExpr() { none() }
|
||||
|
||||
@@ -249,7 +248,7 @@ abstract class AttrRead extends AttrRef, Node, LocalSourceNode {
|
||||
|
||||
/** A simple attribute read, e.g. `object.attr` */
|
||||
private class AttributeReadAsAttrRead extends AttrRead, CfgNode {
|
||||
override Cfg::AttrNode node;
|
||||
override AttrNode node;
|
||||
|
||||
AttributeReadAsAttrRead() { node.isLoad() }
|
||||
|
||||
@@ -286,7 +285,7 @@ private class GetAttrCallAsAttrRead extends AttrRead, CfgNode {
|
||||
* is treated as if it is a read of the attribute `module.attr`, even if `module` is not imported directly.
|
||||
*/
|
||||
private class ModuleAttributeImportAsAttrRead extends AttrRead, CfgNode {
|
||||
override Cfg::ImportMemberNode node;
|
||||
override ImportMemberNode node;
|
||||
|
||||
override Node getObject() { result.asCfgNode() = node.getModule(_) }
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@ overlay[local]
|
||||
module;
|
||||
|
||||
private import python
|
||||
private import semmle.python.controlflow.internal.Cfg as Cfg
|
||||
private import semmle.python.dataflow.new.DataFlow
|
||||
private import semmle.python.dataflow.new.internal.ImportStar
|
||||
|
||||
@@ -68,7 +67,7 @@ module Builtins {
|
||||
DataFlow::CfgNode likelyBuiltin(string name) {
|
||||
exists(Module m |
|
||||
result.getNode() =
|
||||
any(Cfg::NameNode n |
|
||||
any(NameNode n |
|
||||
possible_builtin_accessed_in_module(n, name, m) and
|
||||
not possible_builtin_defined_in_module(name, m)
|
||||
)
|
||||
@@ -88,7 +87,7 @@ module Builtins {
|
||||
* Holds if `n` is an access of a global variable called `name` (which is also the name of a
|
||||
* built-in) inside the module `m`.
|
||||
*/
|
||||
private predicate possible_builtin_accessed_in_module(Cfg::NameNode n, string name, Module m) {
|
||||
private predicate possible_builtin_accessed_in_module(NameNode n, string name, Module m) {
|
||||
n.isGlobal() and
|
||||
n.isLoad() and
|
||||
name = n.getId() and
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
* what callable this call might end up targeting.
|
||||
*
|
||||
* Specifically this means that we cannot use type-backtrackers from the function of a
|
||||
* `Cfg::CallNode`, since there is no `Cfg::CallNode` to backtrack from for `func` in the example
|
||||
* `CallNode`, since there is no `CallNode` to backtrack from for `func` in the example
|
||||
* above.
|
||||
*
|
||||
* Note: This hasn't been 100% realized yet, so we don't currently expose a predicate to
|
||||
@@ -35,7 +35,6 @@ overlay[local?]
|
||||
module;
|
||||
|
||||
private import python
|
||||
private import semmle.python.controlflow.internal.Cfg as Cfg
|
||||
private import DataFlowPublic
|
||||
private import DataFlowPrivate
|
||||
private import FlowSummaryImpl as FlowSummaryImpl
|
||||
@@ -163,7 +162,7 @@ newtype TArgumentPosition =
|
||||
*/
|
||||
TLambdaSelfArgumentPosition() or
|
||||
TPositionalArgumentPosition(int index) {
|
||||
exists(any(Cfg::CallNode c).getArg(index))
|
||||
exists(any(CallNode c).getArg(index))
|
||||
or
|
||||
// since synthetic calls within a summarized callable could use a unique argument
|
||||
// position, we need to ensure we make these available (these are specified as
|
||||
@@ -175,7 +174,7 @@ newtype TArgumentPosition =
|
||||
index = 0
|
||||
} or
|
||||
TKeywordArgumentPosition(string name) {
|
||||
exists(any(Cfg::CallNode c).getArgByName(name))
|
||||
exists(any(CallNode c).getArgByName(name))
|
||||
or
|
||||
// see comment for TPositionalArgumentPosition
|
||||
FlowSummaryImpl::ParsePositions::isParsedKeywordParameterPosition(_, name)
|
||||
@@ -257,12 +256,9 @@ predicate parameterMatch(ParameterPosition ppos, ArgumentPosition apos) {
|
||||
*/
|
||||
overlay[local]
|
||||
predicate isStaticmethod(Function func) {
|
||||
// The decorator is *syntactically* a `Name` "staticmethod" — we don't
|
||||
// care which variable it resolves to. `staticmethod` is a builtin and
|
||||
// is almost never shadowed in a module-level scope; even if a class
|
||||
// redefines `staticmethod` in its body, the class body has not started
|
||||
// executing yet at the decorator position, so Python uses the builtin.
|
||||
func.getADecorator().(Name).getId() = "staticmethod"
|
||||
exists(NameNode id | id.getId() = "staticmethod" and id.isGlobal() |
|
||||
func.getADecorator() = id.getNode()
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -272,9 +268,9 @@ predicate isStaticmethod(Function func) {
|
||||
*/
|
||||
overlay[local]
|
||||
predicate isClassmethod(Function func) {
|
||||
// See `isStaticmethod` for the rationale for matching on the AST `Name`
|
||||
// rather than going via the CFG and `isGlobal()`.
|
||||
func.getADecorator().(Name).getId() = "classmethod"
|
||||
exists(NameNode id | id.getId() = "classmethod" and id.isGlobal() |
|
||||
func.getADecorator() = id.getNode()
|
||||
)
|
||||
or
|
||||
exists(Class cls |
|
||||
cls.getAMethod() = func and
|
||||
@@ -289,8 +285,9 @@ predicate isClassmethod(Function func) {
|
||||
/** Holds if the function `func` has a `property` decorator. */
|
||||
overlay[local]
|
||||
predicate hasPropertyDecorator(Function func) {
|
||||
// See `isStaticmethod` for the rationale for matching on the AST `Name`.
|
||||
func.getADecorator().(Name).getId() = "property"
|
||||
exists(NameNode id | id.getId() = "property" and id.isGlobal() |
|
||||
func.getADecorator() = id.getNode()
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -298,12 +295,10 @@ predicate hasPropertyDecorator(Function func) {
|
||||
*/
|
||||
overlay[local]
|
||||
predicate hasContextmanagerDecorator(Function func) {
|
||||
exists(Cfg::ControlFlowNode contextmanager |
|
||||
contextmanager.(Cfg::NameNode).getId() = "contextmanager" and
|
||||
contextmanager.(Cfg::NameNode).isGlobal()
|
||||
exists(ControlFlowNode contextmanager |
|
||||
contextmanager.(NameNode).getId() = "contextmanager" and contextmanager.(NameNode).isGlobal()
|
||||
or
|
||||
contextmanager.(Cfg::AttrNode).getObject("contextmanager").(Cfg::NameNode).getId() =
|
||||
"contextlib"
|
||||
contextmanager.(AttrNode).getObject("contextmanager").(NameNode).getId() = "contextlib"
|
||||
|
|
||||
func.getADecorator() = contextmanager.getNode()
|
||||
)
|
||||
@@ -319,10 +314,10 @@ predicate hasContextmanagerDecorator(Function func) {
|
||||
*/
|
||||
overlay[local]
|
||||
private predicate hasOverloadDecorator(Function func) {
|
||||
exists(Cfg::ControlFlowNode overload |
|
||||
overload.(Cfg::NameNode).getId() = "overload" and overload.(Cfg::NameNode).isGlobal()
|
||||
exists(ControlFlowNode overload |
|
||||
overload.(NameNode).getId() = "overload" and overload.(NameNode).isGlobal()
|
||||
or
|
||||
overload.(Cfg::AttrNode).getObject("overload").(Cfg::NameNode).isGlobal()
|
||||
overload.(AttrNode).getObject("overload").(NameNode).isGlobal()
|
||||
|
|
||||
func.getADecorator() = overload.getNode()
|
||||
)
|
||||
@@ -541,7 +536,7 @@ class LibraryCallableValue extends DataFlowCallable, TLibraryCallable {
|
||||
// =============================================================================
|
||||
/** Gets a call to `type`. */
|
||||
private CallCfgNode getTypeCall() {
|
||||
exists(Cfg::NameNode id | id.getId() = "type" and id.isGlobal() |
|
||||
exists(NameNode id | id.getId() = "type" and id.isGlobal() |
|
||||
result.getFunction().asCfgNode() = id
|
||||
)
|
||||
}
|
||||
@@ -553,7 +548,7 @@ private CallCfgNode getSuperCall() {
|
||||
// link below), but otherwise only 2 edgecases. Overall it seems ok to ignore this complexity.
|
||||
//
|
||||
// https://github.com/python/cpython/blob/18b1782192f85bd26db89f5bc850f8bee4247c1a/Lib/unittest/mock.py#L48-L50
|
||||
exists(Cfg::NameNode id | id.getId() = "super" and id.isGlobal() |
|
||||
exists(NameNode id | id.getId() = "super" and id.isGlobal() |
|
||||
result.getFunction().asCfgNode() = id
|
||||
)
|
||||
}
|
||||
@@ -1039,7 +1034,7 @@ private module MethodCalls {
|
||||
*/
|
||||
pragma[nomagic]
|
||||
private predicate directCall(
|
||||
Cfg::CallNode call, Function target, string functionName, Class cls, AttrRead attr, Node self
|
||||
CallNode call, Function target, string functionName, Class cls, AttrRead attr, Node self
|
||||
) {
|
||||
target = findFunctionAccordingToMroKnownStartingClass(cls, functionName) and
|
||||
directCall_join(call, functionName, cls, attr, self)
|
||||
@@ -1048,7 +1043,7 @@ private module MethodCalls {
|
||||
/** Extracted to give good join order */
|
||||
pragma[nomagic]
|
||||
private predicate directCall_join(
|
||||
Cfg::CallNode call, string functionName, Class cls, AttrRead attr, Node self
|
||||
CallNode call, string functionName, Class cls, AttrRead attr, Node self
|
||||
) {
|
||||
call.getFunction() = attrReadTracker(attr).asCfgNode() and
|
||||
attr.accesses(self, functionName) and
|
||||
@@ -1065,7 +1060,7 @@ private module MethodCalls {
|
||||
*/
|
||||
pragma[nomagic]
|
||||
private predicate callWithinMethodImplicitSelfOrCls(
|
||||
Cfg::CallNode call, Function target, string functionName, Class classWithMethod, AttrRead attr,
|
||||
CallNode call, Function target, string functionName, Class classWithMethod, AttrRead attr,
|
||||
Node self
|
||||
) {
|
||||
target = findFunctionAccordingToMro(getADirectSubclass*(classWithMethod), functionName) and
|
||||
@@ -1075,7 +1070,7 @@ private module MethodCalls {
|
||||
/** Extracted to give good join order */
|
||||
pragma[nomagic]
|
||||
private predicate callWithinMethodImplicitSelfOrCls_join(
|
||||
Cfg::CallNode call, string functionName, Class classWithMethod, AttrRead attr, Node self
|
||||
CallNode call, string functionName, Class classWithMethod, AttrRead attr, Node self
|
||||
) {
|
||||
call.getFunction() = attrReadTracker(attr).asCfgNode() and
|
||||
attr.accesses(self, functionName) and
|
||||
@@ -1087,7 +1082,7 @@ private module MethodCalls {
|
||||
* resolve the call to a known target (since the only super class might be the
|
||||
* builtin `object`, so we never have the implementation of `__new__` in the DB).
|
||||
*/
|
||||
predicate fromSuperNewCall(Cfg::CallNode call, Class classUsedInSuper, AttrRead attr, Node self) {
|
||||
predicate fromSuperNewCall(CallNode call, Class classUsedInSuper, AttrRead attr, Node self) {
|
||||
fromSuper_join(call, "__new__", classUsedInSuper, attr, self) and
|
||||
self in [classTracker(_), clsArgumentTracker(_)]
|
||||
}
|
||||
@@ -1109,7 +1104,7 @@ private module MethodCalls {
|
||||
*/
|
||||
pragma[nomagic]
|
||||
predicate fromSuper(
|
||||
Cfg::CallNode call, Function target, string functionName, Class classUsedInSuper, AttrRead attr,
|
||||
CallNode call, Function target, string functionName, Class classUsedInSuper, AttrRead attr,
|
||||
Node self
|
||||
) {
|
||||
target = findFunctionAccordingToMro(getNextClassInMro(classUsedInSuper), functionName) and
|
||||
@@ -1119,7 +1114,7 @@ private module MethodCalls {
|
||||
/** Extracted to give good join order */
|
||||
pragma[nomagic]
|
||||
private predicate fromSuper_join(
|
||||
Cfg::CallNode call, string functionName, Class classUsedInSuper, AttrRead attr, Node self
|
||||
CallNode call, string functionName, Class classUsedInSuper, AttrRead attr, Node self
|
||||
) {
|
||||
call.getFunction() = attrReadTracker(attr).asCfgNode() and
|
||||
(
|
||||
@@ -1138,7 +1133,7 @@ private module MethodCalls {
|
||||
)
|
||||
}
|
||||
|
||||
predicate resolveMethodCall(Cfg::CallNode call, Function target, CallType type, Node self) {
|
||||
predicate resolveMethodCall(CallNode call, Function target, CallType type, Node self) {
|
||||
(
|
||||
directCall(call, target, _, _, _, self)
|
||||
or
|
||||
@@ -1185,7 +1180,7 @@ import MethodCalls
|
||||
* NOTE: We have this predicate mostly to be able to compare with old point-to
|
||||
* call-graph resolution. So it could be removed in the future.
|
||||
*/
|
||||
predicate resolveClassCall(Cfg::CallNode call, Class cls) {
|
||||
predicate resolveClassCall(CallNode call, Class cls) {
|
||||
call.getFunction() = classTracker(cls).asCfgNode()
|
||||
or
|
||||
// `cls()` inside a classmethod (which also contains `type(self)()` inside a method)
|
||||
@@ -1215,7 +1210,7 @@ Function invokedFunctionFromClassConstruction(Class cls, string funcName) {
|
||||
*
|
||||
* See https://docs.python.org/3/reference/datamodel.html#object.__call__
|
||||
*/
|
||||
predicate resolveClassInstanceCall(Cfg::CallNode call, Function target, Node self) {
|
||||
predicate resolveClassInstanceCall(CallNode call, Function target, Node self) {
|
||||
exists(Class cls |
|
||||
call.getFunction() = classInstanceTracker(cls).asCfgNode() and
|
||||
target = findFunctionAccordingToMroKnownStartingClass(cls, "__call__")
|
||||
@@ -1234,7 +1229,7 @@ predicate resolveClassInstanceCall(Cfg::CallNode call, Function target, Node sel
|
||||
* Holds if `call` is a call to the `target`, with call-type `type`.
|
||||
*/
|
||||
cached
|
||||
predicate resolveCall(Cfg::CallNode call, Function target, CallType type) {
|
||||
predicate resolveCall(CallNode call, Function target, CallType type) {
|
||||
Stages::DataFlow::ref() and
|
||||
(
|
||||
type instanceof CallTypePlainFunction and
|
||||
@@ -1259,11 +1254,11 @@ predicate resolveCall(Cfg::CallNode call, Function target, CallType type) {
|
||||
// =============================================================================
|
||||
/**
|
||||
* Holds if the argument of `call` at position `apos` is `arg`. This is just a helper
|
||||
* predicate that maps ArgumentPositions to the arguments of the underlying `Cfg::CallNode`.
|
||||
* predicate that maps ArgumentPositions to the arguments of the underlying `CallNode`.
|
||||
*/
|
||||
overlay[local]
|
||||
cached
|
||||
predicate normalCallArg(Cfg::CallNode call, Node arg, ArgumentPosition apos) {
|
||||
predicate normalCallArg(CallNode call, Node arg, ArgumentPosition apos) {
|
||||
exists(int index |
|
||||
apos.isPositional(index) and
|
||||
arg.asCfgNode() = call.getArg(index)
|
||||
@@ -1278,7 +1273,7 @@ predicate normalCallArg(Cfg::CallNode call, Node arg, ArgumentPosition apos) {
|
||||
exists(int index |
|
||||
apos.isStarArgs(index) and
|
||||
arg.asCfgNode() = call.getStarArg() and
|
||||
// since `Cfg::CallNode.getArg` doesn't include `*args`, we need to drop to the AST level
|
||||
// since `CallNode.getArg` doesn't include `*args`, we need to drop to the AST level
|
||||
// to get the index. Notice that we only use the AST for getting the index, so we
|
||||
// don't need to check for dominance in regards to splitting.
|
||||
call.getStarArg().getNode() = call.getNode().getPositionalArg(index).(Starred).getValue()
|
||||
@@ -1352,9 +1347,7 @@ predicate normalCallArg(Cfg::CallNode call, Node arg, ArgumentPosition apos) {
|
||||
* translated into `l.clear()`, and we can still have use-use flow.
|
||||
*/
|
||||
cached
|
||||
predicate getCallArg(
|
||||
Cfg::CallNode call, Function target, CallType type, Node arg, ArgumentPosition apos
|
||||
) {
|
||||
predicate getCallArg(CallNode call, Function target, CallType type, Node arg, ArgumentPosition apos) {
|
||||
Stages::DataFlow::ref() and
|
||||
resolveCall(call, target, type) and
|
||||
(
|
||||
@@ -1447,13 +1440,10 @@ private predicate sameEnclosingCallable(Node node1, Node node2) {
|
||||
// DataFlowCall
|
||||
// =============================================================================
|
||||
newtype TDataFlowCall =
|
||||
TNormalCall(Cfg::CallNode call, Function target, CallType type) {
|
||||
resolveCall(call, target, type) and
|
||||
Cfg::isCanonicalAstNodeRepresentative(call)
|
||||
} or
|
||||
TNormalCall(CallNode call, Function target, CallType type) { resolveCall(call, target, type) } or
|
||||
/** A call to the generated function inside a comprehension */
|
||||
TComprehensionCall(Comp c) or
|
||||
TPotentialLibraryCall(Cfg::CallNode call) { Cfg::isCanonicalAstNodeRepresentative(call) } or
|
||||
TPotentialLibraryCall(CallNode call) or
|
||||
/** A synthesized call inside a summarized callable */
|
||||
TSummaryCall(
|
||||
FlowSummaryImpl::Public::SummarizedCallable c, FlowSummaryImpl::Private::SummaryNode receiver
|
||||
@@ -1473,7 +1463,7 @@ abstract class DataFlowCall extends TDataFlowCall {
|
||||
abstract ArgumentNode getArgument(ArgumentPosition apos);
|
||||
|
||||
/** Get the control flow node representing this call, if any. */
|
||||
abstract Cfg::ControlFlowNode getNode();
|
||||
abstract ControlFlowNode getNode();
|
||||
|
||||
/** Gets the enclosing callable of this call. */
|
||||
DataFlowCallable getEnclosingCallable() { result = getCallableScope(this.getScope()) }
|
||||
@@ -1504,28 +1494,28 @@ abstract class ExtractedDataFlowCall extends DataFlowCall {
|
||||
}
|
||||
|
||||
/**
|
||||
* A resolved call in source code with an underlying `Cfg::CallNode`.
|
||||
* A resolved call in source code with an underlying `CallNode`.
|
||||
*
|
||||
* This is considered normal, compared with special calls such as `obj[0]` calling the
|
||||
* `__getitem__` method on the object. However, this also includes calls that go to the
|
||||
* `__call__` special method.
|
||||
*/
|
||||
class NormalCall extends ExtractedDataFlowCall, TNormalCall {
|
||||
Cfg::CallNode call;
|
||||
CallNode call;
|
||||
Function target;
|
||||
CallType type;
|
||||
|
||||
NormalCall() { this = TNormalCall(call, target, type) }
|
||||
|
||||
override string toString() {
|
||||
// note: if we used toString directly on the Cfg::CallNode we would get
|
||||
// `Cfg::ControlFlowNode for func()`
|
||||
// but the `Cfg::ControlFlowNode` part is just clutter, so we go directly to the AST node
|
||||
// note: if we used toString directly on the CallNode we would get
|
||||
// `ControlFlowNode for func()`
|
||||
// but the `ControlFlowNode` part is just clutter, so we go directly to the AST node
|
||||
// instead.
|
||||
result = call.getNode().toString()
|
||||
}
|
||||
|
||||
override Cfg::ControlFlowNode getNode() { result = call }
|
||||
override ControlFlowNode getNode() { result = call }
|
||||
|
||||
override Scope getScope() { result = call.getScope() }
|
||||
|
||||
@@ -1553,7 +1543,7 @@ class ComprehensionCall extends ExtractedDataFlowCall, TComprehensionCall {
|
||||
|
||||
override string toString() { result = "comprehension call" }
|
||||
|
||||
override Cfg::ControlFlowNode getNode() { result.getNode() = c }
|
||||
override ControlFlowNode getNode() { result.getNode() = c }
|
||||
|
||||
override Scope getScope() { result = c.getScope() }
|
||||
|
||||
@@ -1576,14 +1566,14 @@ class ComprehensionCall extends ExtractedDataFlowCall, TComprehensionCall {
|
||||
* in this class.
|
||||
*/
|
||||
class PotentialLibraryCall extends ExtractedDataFlowCall, TPotentialLibraryCall {
|
||||
Cfg::CallNode call;
|
||||
CallNode call;
|
||||
|
||||
PotentialLibraryCall() { this = TPotentialLibraryCall(call) }
|
||||
|
||||
override string toString() {
|
||||
// note: if we used toString directly on the Cfg::CallNode we would get
|
||||
// `Cfg::ControlFlowNode for func()`
|
||||
// but the `Cfg::ControlFlowNode` part is just clutter, so we go directly to the AST node
|
||||
// note: if we used toString directly on the CallNode we would get
|
||||
// `ControlFlowNode for func()`
|
||||
// but the `ControlFlowNode` part is just clutter, so we go directly to the AST node
|
||||
// instead.
|
||||
result = call.getNode().toString()
|
||||
}
|
||||
@@ -1600,10 +1590,10 @@ class PotentialLibraryCall extends ExtractedDataFlowCall, TPotentialLibraryCall
|
||||
// potential self argument, from `foo.bar()` -- note that this could also just be a
|
||||
// module reference, but we really don't have a good way of knowing :|
|
||||
apos.isSelf() and
|
||||
result.asCfgNode() = call.getFunction().(Cfg::AttrNode).getObject()
|
||||
result.asCfgNode() = call.getFunction().(AttrNode).getObject()
|
||||
}
|
||||
|
||||
override Cfg::ControlFlowNode getNode() { result = call }
|
||||
override ControlFlowNode getNode() { result = call }
|
||||
|
||||
override Scope getScope() { result = call.getScope() }
|
||||
}
|
||||
@@ -1635,7 +1625,7 @@ class SummaryCall extends DataFlowCall, TSummaryCall {
|
||||
|
||||
override ArgumentNode getArgument(ArgumentPosition apos) { none() }
|
||||
|
||||
override Cfg::ControlFlowNode getNode() { none() }
|
||||
override ControlFlowNode getNode() { none() }
|
||||
|
||||
override string toString() { result = "[summary] call to " + receiver + " in " + c }
|
||||
|
||||
@@ -1777,12 +1767,12 @@ private class SummaryPostUpdateNode extends FlowSummaryNode, PostUpdateNodeImpl
|
||||
* This is used for tracking flow through captured variables.
|
||||
*/
|
||||
class SynthCapturedVariablesArgumentNode extends Node, TSynthCapturedVariablesArgumentNode {
|
||||
Cfg::ControlFlowNode callable;
|
||||
ControlFlowNode callable;
|
||||
|
||||
SynthCapturedVariablesArgumentNode() { this = TSynthCapturedVariablesArgumentNode(callable) }
|
||||
|
||||
/** Gets the `Cfg::CallNode` corresponding to this captured variables argument node. */
|
||||
Cfg::CallNode getCallNode() { result.getFunction() = callable }
|
||||
/** Gets the `CallNode` corresponding to this captured variables argument node. */
|
||||
CallNode getCallNode() { result.getFunction() = callable }
|
||||
|
||||
/** Gets the `CfgNode` that corresponds to this synthetic node. */
|
||||
CfgNode getUnderlyingNode() { result.asCfgNode() = callable }
|
||||
@@ -1800,7 +1790,7 @@ class CapturedVariablesArgumentNodeAsArgumentNode extends ArgumentNode,
|
||||
{
|
||||
overlay[global]
|
||||
override predicate argumentOf(DataFlowCall call, ArgumentPosition pos) {
|
||||
exists(Cfg::CallNode callNode | callNode = this.getCallNode() |
|
||||
exists(CallNode callNode | callNode = this.getCallNode() |
|
||||
callNode = call.getNode() and
|
||||
exists(Function target | resolveCall(callNode, target, _) |
|
||||
target = any(VariableCapture::CapturedVariable v).getACapturingScope()
|
||||
@@ -1814,7 +1804,7 @@ class CapturedVariablesArgumentNodeAsArgumentNode extends ArgumentNode,
|
||||
class SynthCapturedVariablesArgumentPostUpdateNode extends PostUpdateNodeImpl,
|
||||
TSynthCapturedVariablesArgumentPostUpdateNode
|
||||
{
|
||||
Cfg::ControlFlowNode callable;
|
||||
ControlFlowNode callable;
|
||||
|
||||
SynthCapturedVariablesArgumentPostUpdateNode() {
|
||||
this = TSynthCapturedVariablesArgumentPostUpdateNode(callable)
|
||||
@@ -1921,8 +1911,8 @@ abstract class ReturnNode extends Node {
|
||||
class ExtractedReturnNode extends ReturnNode, CfgNode {
|
||||
// See `TaintTrackingImplementation::returnFlowStep`
|
||||
ExtractedReturnNode() {
|
||||
node.getNode() = any(Return ret).getValue() or
|
||||
node.getNode() = any(Yield yield)
|
||||
node = any(Return ret).getValue().getAFlowNode() or
|
||||
node = any(Yield yield).getAFlowNode()
|
||||
}
|
||||
|
||||
override ReturnKind getKind() { any() }
|
||||
@@ -1940,7 +1930,7 @@ class ExtractedReturnNode extends ReturnNode, CfgNode {
|
||||
class YieldNodeInContextManagerFunction extends ReturnNode, CfgNode {
|
||||
YieldNodeInContextManagerFunction() {
|
||||
hasContextmanagerDecorator(node.getScope()) and
|
||||
node.getNode() = any(Yield yield).getValue()
|
||||
node = any(Yield yield).getValue().getAFlowNode()
|
||||
}
|
||||
|
||||
override ReturnKind getKind() { any() }
|
||||
|
||||
@@ -2,9 +2,8 @@ overlay[local?]
|
||||
module;
|
||||
|
||||
private import python
|
||||
private import semmle.python.controlflow.internal.Cfg as Cfg
|
||||
private import DataFlowPublic
|
||||
private import semmle.python.dataflow.new.internal.SsaImpl as SsaImpl
|
||||
private import semmle.python.essa.SsaCompute
|
||||
private import semmle.python.dataflow.new.internal.ImportResolution
|
||||
private import FlowSummaryImpl as FlowSummaryImpl
|
||||
private import semmle.python.frameworks.data.ModelsAsData
|
||||
@@ -44,23 +43,13 @@ predicate isArgumentNode(ArgumentNode arg, DataFlowCall c, ArgumentPosition pos)
|
||||
// Nodes
|
||||
//--------
|
||||
overlay[local]
|
||||
predicate isExpressionNode(Cfg::ControlFlowNode node) {
|
||||
node.getNode() instanceof Expr
|
||||
or
|
||||
// `Cfg::ForNode` wraps a `For` statement's iter position, but
|
||||
// overrides `.getNode()` to return the `Py::For` statement (for
|
||||
// legacy parity). The underlying AST is still an `Expr` (the iter
|
||||
// expression); we want a dataflow node here so that for-loop
|
||||
// content reads (`for y in l`) have a source expression node to
|
||||
// read content from.
|
||||
node instanceof Cfg::ForNode
|
||||
}
|
||||
predicate isExpressionNode(ControlFlowNode node) { node.getNode() instanceof Expr }
|
||||
|
||||
// =============================================================================
|
||||
// SyntheticPreUpdateNode
|
||||
// =============================================================================
|
||||
class SyntheticPreUpdateNode extends Node, TSyntheticPreUpdateNode {
|
||||
Cfg::CallNode node;
|
||||
CallNode node;
|
||||
|
||||
SyntheticPreUpdateNode() { this = TSyntheticPreUpdateNode(node) }
|
||||
|
||||
@@ -162,7 +151,7 @@ predicate synthStarArgsElementParameterNodeStoreStep(
|
||||
* been passed in a `**kwargs` argument.
|
||||
*/
|
||||
class SynthDictSplatArgumentNode extends Node, TSynthDictSplatArgumentNode {
|
||||
Cfg::CallNode node;
|
||||
CallNode node;
|
||||
|
||||
SynthDictSplatArgumentNode() { this = TSynthDictSplatArgumentNode(node) }
|
||||
|
||||
@@ -176,7 +165,7 @@ class SynthDictSplatArgumentNode extends Node, TSynthDictSplatArgumentNode {
|
||||
private predicate synthDictSplatArgumentNodeStoreStep(
|
||||
ArgumentNode nodeFrom, DictionaryElementContent c, SynthDictSplatArgumentNode nodeTo
|
||||
) {
|
||||
exists(string name, Cfg::CallNode call, ArgumentPosition keywordPos |
|
||||
exists(string name, CallNode call, ArgumentPosition keywordPos |
|
||||
nodeTo = TSynthDictSplatArgumentNode(call) and
|
||||
getCallArg(call, _, _, nodeFrom, keywordPos) and
|
||||
keywordPos.isKeyword(name) and
|
||||
@@ -196,8 +185,8 @@ private predicate synthDictSplatArgumentNodeStoreStep(
|
||||
*/
|
||||
predicate yieldStoreStep(Node nodeFrom, Content c, Node nodeTo) {
|
||||
exists(Yield yield |
|
||||
nodeTo.asCfgNode().getNode() = yield and
|
||||
nodeFrom.asCfgNode().getNode() = yield.getValue() and
|
||||
nodeTo.asCfgNode() = yield.getAFlowNode() and
|
||||
nodeFrom.asCfgNode() = yield.getValue().getAFlowNode() and
|
||||
// TODO: Consider if this will also need to transfer dictionary content
|
||||
// once dictionary comprehensions are supported.
|
||||
c instanceof ListElementContent
|
||||
@@ -300,7 +289,7 @@ abstract class PostUpdateNodeImpl extends Node {
|
||||
* Synthetic post-update nodes for synthetic nodes need to be listed one by one.
|
||||
*/
|
||||
class SyntheticPostUpdateNode extends PostUpdateNodeImpl, TSyntheticPostUpdateNode {
|
||||
Cfg::ControlFlowNode node;
|
||||
ControlFlowNode node;
|
||||
|
||||
SyntheticPostUpdateNode() { this = TSyntheticPostUpdateNode(node) }
|
||||
|
||||
@@ -344,42 +333,16 @@ module LocalFlow {
|
||||
// `x = f(42)`
|
||||
// nodeFrom is `f(42)`
|
||||
// nodeTo is `x`
|
||||
//
|
||||
// We use the CFG-level `DefinitionNode.getValue()` directly rather
|
||||
// than going through SSA, because the new SSA library prunes write
|
||||
// definitions that have no subsequent read in the same scope (e.g.
|
||||
// a module-level `def f():` whose `f` is only read inside other
|
||||
// functions). The CFG-level link is unconditional.
|
||||
//
|
||||
// The Name-target restriction mirrors legacy ESSA's
|
||||
// `SsaDefinitions::assignment_definition`, which required
|
||||
// `defn.(NameNode).defines(v)`. Subscript and attribute writes
|
||||
// (`x[i] = 42`, `obj.attr = 42`) are intentionally excluded — their
|
||||
// value flow is handled by the content-flow / `AttrWrite` machinery,
|
||||
// not by a local-flow step *into* the Subscript/Attribute expression.
|
||||
// Excluding them is essential for keeping augmented-assignment
|
||||
// targets (`x[i] += 42`) classifiable as `LocalSourceNode` on the
|
||||
// read side: the single canonical CFG node is both a load and a
|
||||
// store, and any incoming local-flow step would disqualify it from
|
||||
// being a local source.
|
||||
exists(Cfg::DefinitionNode def |
|
||||
exists(AssignmentDefinition def |
|
||||
nodeFrom.(CfgNode).getNode() = def.getValue() and
|
||||
nodeTo.(CfgNode).getNode() = def and
|
||||
def instanceof Cfg::NameNode and
|
||||
// Parameter defaults are evaluated in the enclosing scope, while the
|
||||
// parameter itself lives in the function's scope. The cross-scope
|
||||
// edge is provided by `runtimeJumpStep` instead.
|
||||
not exists(Py::Parameter param | def.getNode() = param.asName())
|
||||
nodeTo.(CfgNode).getNode() = def.getDefiningNode()
|
||||
)
|
||||
or
|
||||
// With definition
|
||||
// `with f(42) as x:`
|
||||
// nodeFrom is `f(42)`
|
||||
// nodeTo is `x`
|
||||
exists(
|
||||
With with, Cfg::ControlFlowNode contextManager, SsaImpl::WithDefinition withDef,
|
||||
Cfg::ControlFlowNode var
|
||||
|
|
||||
exists(With with, ControlFlowNode contextManager, WithDefinition withDef, ControlFlowNode var |
|
||||
var = withDef.getDefiningNode()
|
||||
|
|
||||
nodeFrom.(CfgNode).getNode() = contextManager and
|
||||
@@ -398,13 +361,13 @@ module LocalFlow {
|
||||
|
||||
predicate expressionFlowStep(Node nodeFrom, Node nodeTo) {
|
||||
// If expressions
|
||||
nodeFrom.asCfgNode() = nodeTo.asCfgNode().(Cfg::IfExprNode).getAnOperand()
|
||||
nodeFrom.asCfgNode() = nodeTo.asCfgNode().(IfExprNode).getAnOperand()
|
||||
or
|
||||
// Assignment expressions
|
||||
nodeFrom.asCfgNode() = nodeTo.asCfgNode().(Cfg::AssignmentExprNode).getValue()
|
||||
nodeFrom.asCfgNode() = nodeTo.asCfgNode().(AssignmentExprNode).getValue()
|
||||
or
|
||||
// boolean inline expressions such as `x or y` or `x and y`
|
||||
nodeFrom.asCfgNode() = nodeTo.asCfgNode().(Cfg::BoolExprNode).getAnOperand()
|
||||
nodeFrom.asCfgNode() = nodeTo.asCfgNode().(BoolExprNode).getAnOperand()
|
||||
or
|
||||
// Flow inside an unpacking assignment
|
||||
iterableUnpackingFlowStep(nodeFrom, nodeTo)
|
||||
@@ -413,28 +376,12 @@ module LocalFlow {
|
||||
matchFlowStep(nodeFrom, nodeTo)
|
||||
}
|
||||
|
||||
predicate useToNextUse(Cfg::NameNode nodeFrom, Cfg::NameNode nodeTo) {
|
||||
// The SSA-level adjacent-use predicate works on specific CFG variants
|
||||
// (e.g. boolean-outcome `[true]`/`[false]` or emptiness `[empty]`/`[non-empty]`
|
||||
// splits of the same AST node), but dataflow values are insensitive to
|
||||
// those splits — there is at most one `CfgNode` per AST. Project both
|
||||
// ends through `Cfg::isCanonicalAstNodeRepresentative` so all variants
|
||||
// contribute their use-use edges to the canonical pair.
|
||||
exists(Cfg::NameNode fromVariant, Cfg::NameNode toVariant |
|
||||
SsaImpl::AdjacentUses::adjacentUseUse(fromVariant, toVariant) and
|
||||
fromVariant.getNode() = nodeFrom.getNode() and
|
||||
toVariant.getNode() = nodeTo.getNode() and
|
||||
Cfg::isCanonicalAstNodeRepresentative(nodeFrom) and
|
||||
Cfg::isCanonicalAstNodeRepresentative(nodeTo)
|
||||
)
|
||||
predicate useToNextUse(NameNode nodeFrom, NameNode nodeTo) {
|
||||
AdjacentUses::adjacentUseUse(nodeFrom, nodeTo)
|
||||
}
|
||||
|
||||
predicate defToFirstUse(SsaImpl::EssaVariable var, Cfg::NameNode nodeTo) {
|
||||
exists(Cfg::NameNode toVariant |
|
||||
SsaImpl::AdjacentUses::firstUse(var.getDefinition(), toVariant) and
|
||||
toVariant.getNode() = nodeTo.getNode() and
|
||||
Cfg::isCanonicalAstNodeRepresentative(nodeTo)
|
||||
)
|
||||
predicate defToFirstUse(EssaVariable var, NameNode nodeTo) {
|
||||
AdjacentUses::firstUse(var.getDefinition(), nodeTo)
|
||||
}
|
||||
|
||||
predicate useUseFlowStep(Node nodeFrom, Node nodeTo) {
|
||||
@@ -443,14 +390,12 @@ module LocalFlow {
|
||||
// `x = f(y)`
|
||||
// nodeFrom is `y` on first line
|
||||
// nodeTo is `y` on second line
|
||||
exists(SsaImpl::EssaDefinition def, Cfg::NameNode toVariant |
|
||||
nodeFrom.(CfgNode).getNode() = def.(SsaImpl::EssaNodeDefinition).getDefiningNode()
|
||||
exists(EssaDefinition def |
|
||||
nodeFrom.(CfgNode).getNode() = def.(EssaNodeDefinition).getDefiningNode()
|
||||
or
|
||||
nodeFrom.(ScopeEntryDefinitionNode).getDefinition() = def
|
||||
|
|
||||
SsaImpl::AdjacentUses::firstUse(def, toVariant) and
|
||||
toVariant.getNode() = nodeTo.(CfgNode).getNode().getNode() and
|
||||
Cfg::isCanonicalAstNodeRepresentative(nodeTo.(CfgNode).getNode())
|
||||
AdjacentUses::firstUse(def, nodeTo.(CfgNode).getNode())
|
||||
)
|
||||
or
|
||||
// Next use after use
|
||||
@@ -612,9 +557,9 @@ predicate runtimeJumpStep(Node nodeFrom, Node nodeTo) {
|
||||
// a parameter with a default value, since the parameter will be in the scope of the
|
||||
// function, while the default value itself will be in the scope that _defines_ the
|
||||
// function.
|
||||
exists(SsaImpl::ParameterDefinition param |
|
||||
exists(ParameterDefinition param |
|
||||
// note: we go to the _control-flow node_ of the parameter, and not the ESSA node of the parameter, since for type-tracking, the ESSA node is not a LocalSourceNode, so we would get in trouble.
|
||||
nodeFrom.asCfgNode().getNode() = param.getParameter().(Parameter).getDefault() and
|
||||
nodeFrom.asCfgNode() = param.getDefault() and
|
||||
nodeTo.asCfgNode() = param.getDefiningNode()
|
||||
)
|
||||
or
|
||||
@@ -718,7 +663,7 @@ predicate neverSkipInPathGraph(Node n) {
|
||||
// ```
|
||||
// we would end up saying that the path MUST not skip the x in `y = x`, which is just
|
||||
// annoying and doesn't help the path explanation become clearer.
|
||||
n.asCfgNode() = any(SsaImpl::EssaNodeDefinition def).getDefiningNode()
|
||||
n.asCfgNode() = any(EssaNodeDefinition def).getDefiningNode()
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -808,7 +753,7 @@ predicate jumpStepNotSharedWithTypeTracker(Node nodeFrom, Node nodeTo) {
|
||||
* As of 2024-04-02 the type-tracking library only supports precise content, so there is
|
||||
* no reason to include steps for list content right now.
|
||||
*/
|
||||
predicate storeStepCommon(Node nodeFrom, ContentSet c, Node nodeTo) {
|
||||
predicate storeStepCommon(Node nodeFrom, Content c, Node nodeTo) {
|
||||
tupleStoreStep(nodeFrom, c, nodeTo)
|
||||
or
|
||||
dictStoreStep(nodeFrom, c, nodeTo)
|
||||
@@ -822,29 +767,31 @@ predicate storeStepCommon(Node nodeFrom, ContentSet c, Node nodeTo) {
|
||||
* Holds if data can flow from `nodeFrom` to `nodeTo` via an assignment to
|
||||
* content `c`.
|
||||
*/
|
||||
predicate storeStep(Node nodeFrom, ContentSet c, Node nodeTo) {
|
||||
storeStepCommon(nodeFrom, c, nodeTo)
|
||||
predicate storeStep(Node nodeFrom, ContentSet cs, Node nodeTo) {
|
||||
exists(Content c | cs = singleton(c) |
|
||||
storeStepCommon(nodeFrom, c, nodeTo)
|
||||
or
|
||||
listStoreStep(nodeFrom, c, nodeTo)
|
||||
or
|
||||
setStoreStep(nodeFrom, c, nodeTo)
|
||||
or
|
||||
attributeStoreStep(nodeFrom, c, nodeTo)
|
||||
or
|
||||
matchStoreStep(nodeFrom, c, nodeTo)
|
||||
or
|
||||
any(Orm::AdditionalOrmSteps es).storeStep(nodeFrom, c, nodeTo)
|
||||
or
|
||||
synthStarArgsElementParameterNodeStoreStep(nodeFrom, c, nodeTo)
|
||||
or
|
||||
synthDictSplatArgumentNodeStoreStep(nodeFrom, c, nodeTo)
|
||||
or
|
||||
yieldStoreStep(nodeFrom, c, nodeTo)
|
||||
or
|
||||
VariableCapture::storeStep(nodeFrom, c, nodeTo)
|
||||
)
|
||||
or
|
||||
listStoreStep(nodeFrom, c, nodeTo)
|
||||
or
|
||||
setStoreStep(nodeFrom, c, nodeTo)
|
||||
or
|
||||
attributeStoreStep(nodeFrom, c, nodeTo)
|
||||
or
|
||||
matchStoreStep(nodeFrom, c, nodeTo)
|
||||
or
|
||||
any(Orm::AdditionalOrmSteps es).storeStep(nodeFrom, c, nodeTo)
|
||||
or
|
||||
FlowSummaryImpl::Private::Steps::summaryStoreStep(nodeFrom.(FlowSummaryNode).getSummaryNode(), c,
|
||||
FlowSummaryImpl::Private::Steps::summaryStoreStep(nodeFrom.(FlowSummaryNode).getSummaryNode(), cs,
|
||||
nodeTo.(FlowSummaryNode).getSummaryNode())
|
||||
or
|
||||
synthStarArgsElementParameterNodeStoreStep(nodeFrom, c, nodeTo)
|
||||
or
|
||||
synthDictSplatArgumentNodeStoreStep(nodeFrom, c, nodeTo)
|
||||
or
|
||||
yieldStoreStep(nodeFrom, c, nodeTo)
|
||||
or
|
||||
VariableCapture::storeStep(nodeFrom, c, nodeTo)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -927,7 +874,7 @@ predicate listStoreStep(CfgNode nodeFrom, ListElementContent c, CfgNode nodeTo)
|
||||
// nodeFrom is `42`, cfg node
|
||||
// nodeTo is the list, `[..., 42, ...]`, cfg node
|
||||
// c denotes element of list
|
||||
nodeTo.getNode().(Cfg::ListNode).getAnElement() = nodeFrom.getNode() and
|
||||
nodeTo.getNode().(ListNode).getAnElement() = nodeFrom.getNode() and
|
||||
not nodeTo.getNode() instanceof UnpackingAssignmentSequenceTarget and
|
||||
// Suppress unused variable warning
|
||||
c = c
|
||||
@@ -940,7 +887,7 @@ predicate setStoreStep(CfgNode nodeFrom, SetElementContent c, CfgNode nodeTo) {
|
||||
// nodeFrom is `42`, cfg node
|
||||
// nodeTo is the set, `{..., 42, ...}`, cfg node
|
||||
// c denotes element of list
|
||||
nodeTo.getNode().(Cfg::SetNode).getAnElement() = nodeFrom.getNode() and
|
||||
nodeTo.getNode().(SetNode).getAnElement() = nodeFrom.getNode() and
|
||||
// Suppress unused variable warning
|
||||
c = c
|
||||
}
|
||||
@@ -953,7 +900,7 @@ predicate tupleStoreStep(CfgNode nodeFrom, TupleElementContent c, CfgNode nodeTo
|
||||
// nodeTo is the tuple, `(..., 42, ...)`, cfg node
|
||||
// c denotes element of tuple and index of nodeFrom
|
||||
exists(int n |
|
||||
nodeTo.getNode().(Cfg::TupleNode).getElement(n) = nodeFrom.getNode() and
|
||||
nodeTo.getNode().(TupleNode).getElement(n) = nodeFrom.getNode() and
|
||||
not nodeTo.getNode() instanceof UnpackingAssignmentSequenceTarget and
|
||||
c.getIndex() = n
|
||||
)
|
||||
@@ -967,7 +914,7 @@ predicate dictStoreStep(CfgNode nodeFrom, DictionaryElementContent c, Node nodeT
|
||||
// nodeTo is the dict, `{..., "key" = 42, ...}`, cfg node
|
||||
// c denotes element of dictionary and the key `"key"`
|
||||
exists(KeyValuePair item |
|
||||
item = nodeTo.asCfgNode().(Cfg::DictNode).getNode().(Dict).getAnItem() and
|
||||
item = nodeTo.asCfgNode().(DictNode).getNode().(Dict).getAnItem() and
|
||||
nodeFrom.getNode().getNode() = item.getValue() and
|
||||
c.getKey() = item.getKey().(StringLiteral).getS()
|
||||
)
|
||||
@@ -982,9 +929,9 @@ predicate dictStoreStep(CfgNode nodeFrom, DictionaryElementContent c, Node nodeT
|
||||
private predicate moreDictStoreSteps(CfgNode nodeFrom, DictionaryElementContent c, Node nodeTo) {
|
||||
// NOTE: It's important to add logic to the newtype definition of
|
||||
// DictionaryElementContent if you add new cases here.
|
||||
exists(Cfg::SubscriptNode subscript |
|
||||
exists(SubscriptNode subscript |
|
||||
nodeTo.(PostUpdateNode).getPreUpdateNode().asCfgNode() = subscript.getObject() and
|
||||
nodeFrom.asCfgNode() = subscript.(Cfg::DefinitionNode).getValue() and
|
||||
nodeFrom.asCfgNode() = subscript.(DefinitionNode).getValue() and
|
||||
c.getKey() = subscript.getIndex().getNode().(StringLiteral).getText()
|
||||
)
|
||||
or
|
||||
@@ -997,8 +944,8 @@ private predicate moreDictStoreSteps(CfgNode nodeFrom, DictionaryElementContent
|
||||
}
|
||||
|
||||
predicate dictClearStep(Node node, DictionaryElementContent c) {
|
||||
exists(Cfg::SubscriptNode subscript |
|
||||
subscript instanceof Cfg::DefinitionNode and
|
||||
exists(SubscriptNode subscript |
|
||||
subscript instanceof DefinitionNode and
|
||||
node.asCfgNode() = subscript.getObject() and
|
||||
c.getKey() = subscript.getIndex().getNode().(StringLiteral).getText()
|
||||
)
|
||||
@@ -1040,7 +987,7 @@ predicate attributeStoreStep(Node nodeFrom, AttributeContent c, Node nodeTo) {
|
||||
/**
|
||||
* Subset of `readStep` that should be shared with type-tracking.
|
||||
*/
|
||||
predicate readStepCommon(Node nodeFrom, ContentSet c, Node nodeTo) {
|
||||
predicate readStepCommon(Node nodeFrom, Content c, Node nodeTo) {
|
||||
subscriptReadStep(nodeFrom, c, nodeTo)
|
||||
or
|
||||
iterableUnpackingReadStep(nodeFrom, c, nodeTo)
|
||||
@@ -1049,21 +996,25 @@ predicate readStepCommon(Node nodeFrom, ContentSet c, Node nodeTo) {
|
||||
/**
|
||||
* Holds if data can flow from `nodeFrom` to `nodeTo` via a read of content `c`.
|
||||
*/
|
||||
predicate readStep(Node nodeFrom, ContentSet c, Node nodeTo) {
|
||||
readStepCommon(nodeFrom, c, nodeTo)
|
||||
predicate readStep(Node nodeFrom, ContentSet cs, Node nodeTo) {
|
||||
exists(Content c | cs = singleton(c) |
|
||||
readStepCommon(nodeFrom, c, nodeTo)
|
||||
or
|
||||
matchReadStep(nodeFrom, c, nodeTo)
|
||||
or
|
||||
forReadStep(nodeFrom, c, nodeTo)
|
||||
or
|
||||
attributeReadStep(nodeFrom, c, nodeTo)
|
||||
or
|
||||
synthDictSplatParameterNodeReadStep(nodeFrom, c, nodeTo)
|
||||
or
|
||||
VariableCapture::readStep(nodeFrom, c, nodeTo)
|
||||
)
|
||||
or
|
||||
matchReadStep(nodeFrom, c, nodeTo)
|
||||
or
|
||||
forReadStep(nodeFrom, c, nodeTo)
|
||||
or
|
||||
attributeReadStep(nodeFrom, c, nodeTo)
|
||||
or
|
||||
FlowSummaryImpl::Private::Steps::summaryReadStep(nodeFrom.(FlowSummaryNode).getSummaryNode(), c,
|
||||
FlowSummaryImpl::Private::Steps::summaryReadStep(nodeFrom.(FlowSummaryNode).getSummaryNode(), cs,
|
||||
nodeTo.(FlowSummaryNode).getSummaryNode())
|
||||
or
|
||||
synthDictSplatParameterNodeReadStep(nodeFrom, c, nodeTo)
|
||||
or
|
||||
VariableCapture::readStep(nodeFrom, c, nodeTo)
|
||||
Conversions::readStep(nodeFrom, cs, nodeTo)
|
||||
}
|
||||
|
||||
/** Data flows from a sequence to a subscript of the sequence. */
|
||||
@@ -1073,7 +1024,7 @@ predicate subscriptReadStep(CfgNode nodeFrom, Content c, CfgNode nodeTo) {
|
||||
// nodeFrom is `l`, cfg node
|
||||
// nodeTo is `l[3]`, cfg node
|
||||
// c is compatible with 3
|
||||
nodeFrom.getNode() = nodeTo.getNode().(Cfg::SubscriptNode).getObject() and
|
||||
nodeFrom.getNode() = nodeTo.getNode().(SubscriptNode).getObject() and
|
||||
(
|
||||
c instanceof ListElementContent
|
||||
or
|
||||
@@ -1082,10 +1033,10 @@ predicate subscriptReadStep(CfgNode nodeFrom, Content c, CfgNode nodeTo) {
|
||||
c instanceof DictionaryElementAnyContent
|
||||
or
|
||||
c.(TupleElementContent).getIndex() =
|
||||
nodeTo.getNode().(Cfg::SubscriptNode).getIndex().getNode().(IntegerLiteral).getValue()
|
||||
nodeTo.getNode().(SubscriptNode).getIndex().getNode().(IntegerLiteral).getValue()
|
||||
or
|
||||
c.(DictionaryElementContent).getKey() =
|
||||
nodeTo.getNode().(Cfg::SubscriptNode).getIndex().getNode().(StringLiteral).getS()
|
||||
nodeTo.getNode().(SubscriptNode).getIndex().getNode().(StringLiteral).getS()
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1119,23 +1070,68 @@ predicate attributeReadStep(Node nodeFrom, AttributeContent c, AttrRead nodeTo)
|
||||
nodeTo.accesses(nodeFrom, c.getAttribute())
|
||||
}
|
||||
|
||||
module Conversions {
|
||||
private import semmle.python.Concepts
|
||||
|
||||
predicate decoderReadStep(Node nodeFrom, ContentSet c, Node nodeTo) {
|
||||
exists(Decoding decoding |
|
||||
nodeFrom = decoding.getAnInput() and
|
||||
nodeTo = decoding.getOutput()
|
||||
) and
|
||||
c.isAnyTupleOrDictionaryElement()
|
||||
}
|
||||
|
||||
predicate encoderReadStep(Node nodeFrom, ContentSet c, Node nodeTo) {
|
||||
exists(Encoding encoding |
|
||||
nodeFrom = encoding.getAnInput() and
|
||||
nodeTo = encoding.getOutput()
|
||||
) and
|
||||
c.isAnyTupleOrDictionaryElement()
|
||||
}
|
||||
|
||||
predicate formatReadStep(Node nodeFrom, ContentSet c, Node nodeTo) {
|
||||
// % formatting
|
||||
exists(BinaryExprNode fmt | fmt = nodeTo.asCfgNode() |
|
||||
fmt.getOp() instanceof Mod and
|
||||
fmt.getRight() = nodeFrom.asCfgNode()
|
||||
) and
|
||||
c.isAnyTupleElement()
|
||||
or
|
||||
// format_map
|
||||
// see https://docs.python.org/3/library/stdtypes.html#str.format_map
|
||||
nodeTo.(MethodCallNode).calls(_, "format_map") and
|
||||
nodeTo.(MethodCallNode).getArg(0) = nodeFrom and
|
||||
c.isAnyDictionaryElement()
|
||||
}
|
||||
|
||||
predicate readStep(Node nodeFrom, ContentSet c, Node nodeTo) {
|
||||
decoderReadStep(nodeFrom, c, nodeTo)
|
||||
or
|
||||
encoderReadStep(nodeFrom, c, nodeTo)
|
||||
or
|
||||
formatReadStep(nodeFrom, c, nodeTo)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if values stored inside content `c` are cleared at node `n`. For example,
|
||||
* any value stored inside `f` is cleared at the pre-update node associated with `x`
|
||||
* in `x.f = newValue`.
|
||||
*/
|
||||
predicate clearsContent(Node n, ContentSet c) {
|
||||
matchClearStep(n, c)
|
||||
predicate clearsContent(Node n, ContentSet cs) {
|
||||
exists(Content c | cs = singleton(c) |
|
||||
matchClearStep(n, c)
|
||||
or
|
||||
attributeClearStep(n, c)
|
||||
or
|
||||
dictClearStep(n, c)
|
||||
or
|
||||
dictSplatParameterNodeClearStep(n, c)
|
||||
or
|
||||
VariableCapture::clearsContent(n, c)
|
||||
)
|
||||
or
|
||||
attributeClearStep(n, c)
|
||||
or
|
||||
dictClearStep(n, c)
|
||||
or
|
||||
FlowSummaryImpl::Private::Steps::summaryClearsContent(n.(FlowSummaryNode).getSummaryNode(), c)
|
||||
or
|
||||
dictSplatParameterNodeClearStep(n, c)
|
||||
or
|
||||
VariableCapture::clearsContent(n, c)
|
||||
FlowSummaryImpl::Private::Steps::summaryClearsContent(n.(FlowSummaryNode).getSummaryNode(), cs)
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -5,12 +5,11 @@ overlay[local]
|
||||
module;
|
||||
|
||||
private import python
|
||||
private import semmle.python.controlflow.internal.Cfg as Cfg
|
||||
private import DataFlowPrivate
|
||||
import semmle.python.dataflow.new.TypeTracking
|
||||
import Attributes
|
||||
import LocalSources
|
||||
private import semmle.python.dataflow.new.internal.SsaImpl as SsaImpl
|
||||
private import semmle.python.essa.SsaCompute
|
||||
private import semmle.python.dataflow.new.internal.ImportStar
|
||||
private import semmle.python.frameworks.data.ModelsAsData
|
||||
private import FlowSummaryImpl as FlowSummaryImpl
|
||||
@@ -28,21 +27,16 @@ private import semmle.python.frameworks.data.ModelsAsData
|
||||
overlay[local]
|
||||
newtype TNode =
|
||||
/** A node corresponding to a control flow node. */
|
||||
TCfgNode(Cfg::ControlFlowNode node) {
|
||||
(
|
||||
isExpressionNode(node)
|
||||
or
|
||||
node.getNode() instanceof Pattern
|
||||
) and
|
||||
Cfg::isCanonicalAstNodeRepresentative(node)
|
||||
TCfgNode(ControlFlowNode node) {
|
||||
isExpressionNode(node)
|
||||
or
|
||||
node.getNode() instanceof Pattern
|
||||
} or
|
||||
/**
|
||||
* A node corresponding to a scope entry definition. That is, the value of a variable
|
||||
* as it enters a scope.
|
||||
*/
|
||||
TScopeEntryDefinitionNode(SsaImpl::ScopeEntryDefinition def) {
|
||||
not def.getScope() instanceof Module
|
||||
} or
|
||||
TScopeEntryDefinitionNode(ScopeEntryDefinition def) { not def.getScope() instanceof Module } or
|
||||
/**
|
||||
* A synthetic node representing the value of an object before a state change.
|
||||
*
|
||||
@@ -53,39 +47,36 @@ newtype TNode =
|
||||
// NOTE: since we can't rely on the call graph, but we want to have synthetic
|
||||
// pre-update nodes for class calls, we end up getting synthetic pre-update nodes for
|
||||
// ALL calls :|
|
||||
TSyntheticPreUpdateNode(Cfg::CallNode call) { Cfg::isCanonicalAstNodeRepresentative(call) } or
|
||||
TSyntheticPreUpdateNode(CallNode call) or
|
||||
/**
|
||||
* A synthetic node representing the value of an object after a state change.
|
||||
* See QLDoc for `PostUpdateNode`.
|
||||
*/
|
||||
TSyntheticPostUpdateNode(Cfg::ControlFlowNode node) {
|
||||
Cfg::isCanonicalAstNodeRepresentative(node) and
|
||||
(
|
||||
exists(Cfg::CallNode call |
|
||||
node = call.getArg(_)
|
||||
or
|
||||
node = call.getArgByName(_)
|
||||
or
|
||||
// `self` argument when handling class instance calls (`__call__` special method))
|
||||
node = call.getFunction()
|
||||
)
|
||||
TSyntheticPostUpdateNode(ControlFlowNode node) {
|
||||
exists(CallNode call |
|
||||
node = call.getArg(_)
|
||||
or
|
||||
node = any(Cfg::AttrNode a).getObject()
|
||||
node = call.getArgByName(_)
|
||||
or
|
||||
node = any(Cfg::SubscriptNode s).getObject()
|
||||
or
|
||||
// self parameter when used implicitly in `super()`
|
||||
exists(Class cls, Function func, SsaImpl::ParameterDefinition def |
|
||||
func = cls.getAMethod() and
|
||||
not isStaticmethod(func) and
|
||||
// this matches what we do in ExtractedParameterNode
|
||||
def.getDefiningNode() = node and
|
||||
def.getParameter() = func.getArg(0)
|
||||
)
|
||||
or
|
||||
// the iterable argument to the implicit comprehension function
|
||||
node.getNode() = any(Comp c).getIterable()
|
||||
// `self` argument when handling class instance calls (`__call__` special method))
|
||||
node = call.getFunction()
|
||||
)
|
||||
or
|
||||
node = any(AttrNode a).getObject()
|
||||
or
|
||||
node = any(SubscriptNode s).getObject()
|
||||
or
|
||||
// self parameter when used implicitly in `super()`
|
||||
exists(Class cls, Function func, ParameterDefinition def |
|
||||
func = cls.getAMethod() and
|
||||
not isStaticmethod(func) and
|
||||
// this matches what we do in ExtractedParameterNode
|
||||
def.getDefiningNode() = node and
|
||||
def.getParameter() = func.getArg(0)
|
||||
)
|
||||
or
|
||||
// the iterable argument to the implicit comprehension function
|
||||
node.getNode() = any(Comp c).getIterable()
|
||||
} or
|
||||
/** A node representing a global (module-level) variable in a specific module. */
|
||||
TModuleVariableNode(Module m, GlobalVariable v) { v.getScope() = m } or
|
||||
@@ -121,9 +112,7 @@ newtype TNode =
|
||||
exists(ParameterPosition ppos | ppos.isStarArgs(_) | exists(callable.getParameter(ppos)))
|
||||
} or
|
||||
/** A synthetic node to capture keyword arguments that are passed to a `**kwargs` parameter. */
|
||||
TSynthDictSplatArgumentNode(Cfg::CallNode call) {
|
||||
exists(call.getArgByName(_)) and Cfg::isCanonicalAstNodeRepresentative(call)
|
||||
} or
|
||||
TSynthDictSplatArgumentNode(CallNode call) { exists(call.getArgByName(_)) } or
|
||||
/** A synthetic node to allow flow to keyword parameters from a `**kwargs` argument. */
|
||||
TSynthDictSplatParameterNode(DataFlowCallable callable) {
|
||||
exists(ParameterPosition ppos | ppos.isKeyword(_) | exists(callable.getParameter(ppos)))
|
||||
@@ -139,17 +128,15 @@ newtype TNode =
|
||||
* A synthetic node representing the values of the variables captured
|
||||
* by the callable being called.
|
||||
*/
|
||||
TSynthCapturedVariablesArgumentNode(Cfg::ControlFlowNode callable) {
|
||||
callable = any(Cfg::CallNode c).getFunction() and
|
||||
Cfg::isCanonicalAstNodeRepresentative(callable)
|
||||
TSynthCapturedVariablesArgumentNode(ControlFlowNode callable) {
|
||||
callable = any(CallNode c).getFunction()
|
||||
} or
|
||||
/**
|
||||
* A synthetic node representing the values of the variables captured
|
||||
* by the callable being called, after the output has been computed.
|
||||
*/
|
||||
TSynthCapturedVariablesArgumentPostUpdateNode(Cfg::ControlFlowNode callable) {
|
||||
callable = any(Cfg::CallNode c).getFunction() and
|
||||
Cfg::isCanonicalAstNodeRepresentative(callable)
|
||||
TSynthCapturedVariablesArgumentPostUpdateNode(ControlFlowNode callable) {
|
||||
callable = any(CallNode c).getFunction()
|
||||
} or
|
||||
/** A synthetic node representing the values of variables captured by a comprehension. */
|
||||
TSynthCompCapturedVariablesArgumentNode(Comp comp) {
|
||||
@@ -207,7 +194,7 @@ class Node extends TNode {
|
||||
}
|
||||
|
||||
/** Gets the control-flow node corresponding to this node, if any. */
|
||||
Cfg::ControlFlowNode asCfgNode() { none() }
|
||||
ControlFlowNode asCfgNode() { none() }
|
||||
|
||||
/** Gets the expression corresponding to this node, if any. */
|
||||
Expr asExpr() { none() }
|
||||
@@ -220,14 +207,14 @@ class Node extends TNode {
|
||||
|
||||
/** A data-flow node corresponding to a control-flow node. */
|
||||
class CfgNode extends Node, TCfgNode {
|
||||
Cfg::ControlFlowNode node;
|
||||
ControlFlowNode node;
|
||||
|
||||
CfgNode() { this = TCfgNode(node) }
|
||||
|
||||
/** Gets the `Cfg::ControlFlowNode` represented by this data-flow node. */
|
||||
Cfg::ControlFlowNode getNode() { result = node }
|
||||
/** Gets the `ControlFlowNode` represented by this data-flow node. */
|
||||
ControlFlowNode getNode() { result = node }
|
||||
|
||||
override Cfg::ControlFlowNode asCfgNode() { result = node }
|
||||
override ControlFlowNode asCfgNode() { result = node }
|
||||
|
||||
/** Gets a textual representation of this element. */
|
||||
override string toString() { result = node.toString() }
|
||||
@@ -237,9 +224,9 @@ class CfgNode extends Node, TCfgNode {
|
||||
override Location getLocation() { result = node.getLocation() }
|
||||
}
|
||||
|
||||
/** A data-flow node corresponding to a `Cfg::CallNode` in the control-flow graph. */
|
||||
/** A data-flow node corresponding to a `CallNode` in the control-flow graph. */
|
||||
class CallCfgNode extends CfgNode, LocalSourceNode {
|
||||
override Cfg::CallNode node;
|
||||
override CallNode node;
|
||||
|
||||
/**
|
||||
* Gets the data-flow node for the function component of the call corresponding to this data-flow
|
||||
@@ -320,15 +307,15 @@ ExprNode exprNode(DataFlowExpr e) { result.getNode().getNode() = e }
|
||||
* as it enters a scope.
|
||||
*/
|
||||
class ScopeEntryDefinitionNode extends Node, TScopeEntryDefinitionNode {
|
||||
SsaImpl::ScopeEntryDefinition def;
|
||||
ScopeEntryDefinition def;
|
||||
|
||||
ScopeEntryDefinitionNode() { this = TScopeEntryDefinitionNode(def) }
|
||||
|
||||
/** Gets the `SsaImpl::ScopeEntryDefinition` associated with this node. */
|
||||
SsaImpl::ScopeEntryDefinition getDefinition() { result = def }
|
||||
/** Gets the `ScopeEntryDefinition` associated with this node. */
|
||||
ScopeEntryDefinition getDefinition() { result = def }
|
||||
|
||||
/** Gets the source variable represented by this node. */
|
||||
SsaImpl::SsaSourceVariable getVariable() { result = def.getSourceVariable() }
|
||||
SsaSourceVariable getVariable() { result = def.getSourceVariable() }
|
||||
|
||||
override Location getLocation() { result = def.getLocation() }
|
||||
|
||||
@@ -350,7 +337,7 @@ class ParameterNode extends Node instanceof ParameterNodeImpl {
|
||||
/** A parameter node found in the source code (not in a summary). */
|
||||
class ExtractedParameterNode extends ParameterNodeImpl, CfgNode {
|
||||
//, LocalSourceNode {
|
||||
SsaImpl::ParameterDefinition def;
|
||||
ParameterDefinition def;
|
||||
|
||||
ExtractedParameterNode() { node = def.getDefiningNode() }
|
||||
|
||||
@@ -381,10 +368,10 @@ Node getCallArgApproximation() {
|
||||
exists(Class c | result.asExpr() = c.getAMethod().getArg(0))
|
||||
or
|
||||
// the object part of an attribute expression (which might be a bound method)
|
||||
result.asCfgNode() = any(Cfg::AttrNode a).getObject()
|
||||
result.asCfgNode() = any(AttrNode a).getObject()
|
||||
or
|
||||
// the function part of any call
|
||||
result.asCfgNode() = any(Cfg::CallNode c).getFunction()
|
||||
result.asCfgNode() = any(CallNode c).getFunction()
|
||||
}
|
||||
|
||||
/** Gets the extracted argument nodes that do not rely on `getCallArg`. */
|
||||
@@ -393,7 +380,7 @@ private Node implicitArgumentNode() {
|
||||
normalCallArg(_, result, _)
|
||||
or
|
||||
// and self arguments
|
||||
result.asCfgNode() = any(Cfg::CallNode c).getFunction().(Cfg::AttrNode).getObject()
|
||||
result.asCfgNode() = any(CallNode c).getFunction().(AttrNode).getObject()
|
||||
or
|
||||
// for comprehensions, we allow the synthetic `iterable` argument
|
||||
result.asExpr() = any(Comp c).getIterable()
|
||||
@@ -498,24 +485,21 @@ class ModuleVariableNode extends Node, TModuleVariableNode {
|
||||
|
||||
/** Gets a node that reads this variable, excluding reads that happen through `from ... import *`. */
|
||||
Node getALocalRead() {
|
||||
result.asCfgNode().getNode() = var.getALoad() and
|
||||
result.asCfgNode() = var.getALoad().getAFlowNode() and
|
||||
not result.getScope() = mod
|
||||
}
|
||||
|
||||
/** Gets a CFG node that corresponds to an assignment of this global variable. */
|
||||
/** Gets an `EssaNode` that corresponds to an assignment of this global variable. */
|
||||
Node getAWrite() {
|
||||
exists(Cfg::NameNode n |
|
||||
n.defines(var) and
|
||||
result.asCfgNode() = n
|
||||
)
|
||||
any(EssaNodeDefinition def).definedBy(var, result.asCfgNode().(DefinitionNode))
|
||||
}
|
||||
|
||||
/** Gets the possible values of the variable at the end of import time */
|
||||
CfgNode getADefiningWrite() {
|
||||
exists(SsaImpl::EssaVariable def |
|
||||
def = any(SsaImpl::EssaVariable ssa_var).getAnUltimateDefinition() and
|
||||
def.getDefinition().(SsaImpl::EssaNodeDefinition).getDefiningNode() = result.asCfgNode() and
|
||||
def.getSourceVariable().getVariable() = var
|
||||
exists(SsaVariable def |
|
||||
def = any(SsaVariable ssa_var).getAnUltimateDefinition() and
|
||||
def.getDefinition() = result.asCfgNode() and
|
||||
def.getVariable() = var
|
||||
)
|
||||
}
|
||||
|
||||
@@ -532,7 +516,7 @@ private ModuleVariableNode import_star_read(Node n) {
|
||||
overlay[global]
|
||||
pragma[nomagic]
|
||||
private predicate resolved_import_star_module(Module m, string name, Node n) {
|
||||
exists(Cfg::NameNode nn | nn = n.asCfgNode() |
|
||||
exists(NameNode nn | nn = n.asCfgNode() |
|
||||
ImportStar::importStarResolvesTo(pragma[only_bind_into](nn), m) and
|
||||
nn.getId() = name
|
||||
)
|
||||
@@ -590,116 +574,88 @@ class StarPatternElementNode extends Node, TStarPatternElementNode {
|
||||
}
|
||||
|
||||
/**
|
||||
* A node that participates in a conditional split: a CFG node whose
|
||||
* evaluation outcome (true/false) is used to choose between two
|
||||
* successor basic blocks. In the new shared CFG, such nodes appear in
|
||||
* pairs of `isAfterTrue`/`isAfterFalse` annotated CFG nodes.
|
||||
* Gets a node that controls whether other nodes are evaluated.
|
||||
*
|
||||
* Users typically obtain a `GuardNode` by casting from a more specific
|
||||
* Cfg type: `g.(Cfg::CallNode)` for a call-based check, etc.
|
||||
* In the base case, this is the last node of `conditionBlock`, and `flipped` is `false`.
|
||||
* This definition accounts for (short circuting) `and`- and `or`-expressions, as the structure
|
||||
* of basic blocks will reflect their semantics.
|
||||
*
|
||||
* This replaces the legacy (pre-shared-CFG) `GuardNode`/`flipped`
|
||||
* machinery: the shared CFG carries outcome information structurally
|
||||
* (via `isAfterTrue`/`isAfterFalse`), so no separate polarity field
|
||||
* is required.
|
||||
* However, in the program
|
||||
* ```python
|
||||
* if not is_safe(path):
|
||||
* return
|
||||
* ```
|
||||
* the last node in the `ConditionBlock` is `not is_safe(path)`.
|
||||
*
|
||||
* We would like to consider also `is_safe(path)` a guard node, albeit with `flipped` being `true`.
|
||||
* Thus we recurse through `not`-expressions.
|
||||
*/
|
||||
class GuardNode extends Cfg::ControlFlowNode {
|
||||
GuardNode() {
|
||||
// This is the canonical (post-order) version of an AST node, and
|
||||
// some `[true]`/`[false]` variant of the same AST exists. We
|
||||
// include the canonical node because users identify guards by
|
||||
// their AST (`g.(Cfg::CallNode)` etc.), and the outcome-tagged
|
||||
// variants are accessed by `outcomeOfGuard` below.
|
||||
exists(Cfg::ControlFlowNode outcome |
|
||||
outcome.getNode() = this.getNode() and
|
||||
(outcome.isAfterTrue(_) or outcome.isAfterFalse(_))
|
||||
ControlFlowNode guardNode(ConditionBlock conditionBlock, boolean flipped) {
|
||||
// Base case: the last node truly does determine which successor is chosen
|
||||
result = conditionBlock.getLastNode() and
|
||||
flipped = false
|
||||
or
|
||||
// Recursive cases:
|
||||
// if a guard node is a `not`-expression,
|
||||
// the operand is also a guard node, but with inverted polarity.
|
||||
exists(UnaryExprNode notNode |
|
||||
result = notNode.getOperand() and
|
||||
notNode.getNode().getOp() instanceof Not
|
||||
|
|
||||
notNode = guardNode(conditionBlock, flipped.booleanNot())
|
||||
)
|
||||
or
|
||||
// if a guard node is compared to a boolean literal,
|
||||
// the other operand is also a guard node,
|
||||
// but with polarity depending on the literal (and on the comparison).
|
||||
exists(CompareNode cmpNode, Cmpop op, ControlFlowNode b, boolean should_flip |
|
||||
(
|
||||
cmpNode.operands(result, op, b) or
|
||||
cmpNode.operands(b, op, result)
|
||||
) and
|
||||
not result.getNode() instanceof BooleanLiteral and
|
||||
(
|
||||
// comparing to the boolean
|
||||
(op instanceof Eq or op instanceof Is) and
|
||||
// we should flip if the value compared against, here the value of `b`, is false
|
||||
should_flip = b.getNode().(BooleanLiteral).booleanValue().booleanNot()
|
||||
or
|
||||
// comparing to the negation of the boolean
|
||||
(op instanceof NotEq or op instanceof IsNot) and
|
||||
// again, we should flip if the value compared against, here the value of `not b`, is false.
|
||||
// That is, if the value of `b` is true.
|
||||
should_flip = b.getNode().(BooleanLiteral).booleanValue()
|
||||
)
|
||||
or
|
||||
// Or: this IS one of the outcome-tagged variants, supporting
|
||||
// users who want to query the split point directly.
|
||||
this.isAfterTrue(_)
|
||||
or
|
||||
this.isAfterFalse(_)
|
||||
}
|
||||
|
||||
/** Holds if this guard controls block `b` upon evaluating to `branch`. */
|
||||
predicate controlsBlock(Cfg::BasicBlock b, boolean branch) {
|
||||
branch in [true, false] and
|
||||
exists(Cfg::ControlFlowNode outcomeNode |
|
||||
outcomeOfGuard(this, outcomeNode, branch) and
|
||||
outcomeNode.getBasicBlock().dominates(b)
|
||||
)
|
||||
}
|
||||
|
|
||||
// we flip `flipped` according to `should_flip` via the formula `flipped xor should_flip`.
|
||||
flipped in [true, false] and
|
||||
cmpNode = guardNode(conditionBlock, flipped.booleanXor(should_flip))
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if some execution that arrives at `outcomeNode` corresponds
|
||||
* to `guard` having evaluated to `branch`.
|
||||
* A node that controls whether other nodes are evaluated.
|
||||
*
|
||||
* For a direct guard `if g:`, the outcome node is `g` itself with
|
||||
* `isAfterTrue`/`isAfterFalse`. For wrapped guards like `not g` or
|
||||
* `g == True`, the outcome is on the wrapping expression with an
|
||||
* appropriate polarity transform — we follow those wrappers up the
|
||||
* AST to find the outermost expression that carries an actual
|
||||
* `isAfterTrue`/`isAfterFalse` outcome.
|
||||
* The field `flipped` allows us to match `GuardNode`s underneath
|
||||
* `not`-expressions and still choose the appropriate branch.
|
||||
*/
|
||||
private predicate outcomeOfGuard(
|
||||
Cfg::ControlFlowNode guard, Cfg::ControlFlowNode outcomeNode, boolean branch
|
||||
) {
|
||||
// Base case: the guard itself splits — the outcome node is the
|
||||
// first node of an outcome BB, with matching outcome label.
|
||||
// (The shared CFG also marks inner expressions with outcome flags
|
||||
// for analysis purposes, but only "splitting" nodes — those that
|
||||
// actually start an outcome BB — are valid guards on their own.)
|
||||
// The guard must be the canonical AST representative for its AST:
|
||||
// each AST expression has at most one canonical representative, and
|
||||
// admitting non-canonical outcome-tagged variants (e.g. `After l [false]`
|
||||
// when the canonical is `After l [true]`) would let them spuriously
|
||||
// "control" the `[true]`-tagged BB via the AST-matching join below.
|
||||
Cfg::isCanonicalAstNodeRepresentative(guard) and
|
||||
outcomeNode.getNode() = guard.getNode() and
|
||||
outcomeNode = outcomeNode.getBasicBlock().firstNode() and
|
||||
(
|
||||
outcomeNode.isAfterTrue(_) and branch = true
|
||||
or
|
||||
outcomeNode.isAfterFalse(_) and branch = false
|
||||
)
|
||||
or
|
||||
// Recursive: `not guard` — same outcome split as `guard`, flipped.
|
||||
exists(Cfg::UnaryExprNode notNode, boolean notBranch |
|
||||
notNode.getOperand().getNode() = guard.getNode() and
|
||||
notNode.getNode().getOp() instanceof Not and
|
||||
outcomeOfGuard(notNode, outcomeNode, notBranch) and
|
||||
branch = notBranch.booleanNot()
|
||||
)
|
||||
or
|
||||
// Recursive: comparisons against a boolean literal.
|
||||
exists(
|
||||
Cfg::CompareNode cmpNode, Cmpop op, Cfg::ControlFlowNode otherOperand,
|
||||
Cfg::ControlFlowNode guardOperand, boolean polarity, boolean cmpBranch
|
||||
|
|
||||
guardOperand.getNode() = guard.getNode() and
|
||||
(
|
||||
cmpNode.operands(guardOperand, op, otherOperand) or
|
||||
cmpNode.operands(otherOperand, op, guardOperand)
|
||||
) and
|
||||
not guard.getNode() instanceof BooleanLiteral and
|
||||
(
|
||||
(op instanceof Eq or op instanceof Is) and
|
||||
polarity = otherOperand.getNode().(BooleanLiteral).booleanValue()
|
||||
or
|
||||
(op instanceof NotEq or op instanceof IsNot) and
|
||||
polarity = otherOperand.getNode().(BooleanLiteral).booleanValue().booleanNot()
|
||||
) and
|
||||
outcomeOfGuard(cmpNode, outcomeNode, cmpBranch) and
|
||||
branch = cmpBranch.booleanXor(polarity.booleanNot())
|
||||
)
|
||||
class GuardNode extends ControlFlowNode {
|
||||
ConditionBlock conditionBlock;
|
||||
boolean flipped;
|
||||
|
||||
GuardNode() { this = guardNode(conditionBlock, flipped) }
|
||||
|
||||
/** Holds if this guard controls block `b` upon evaluating to `branch`. */
|
||||
predicate controlsBlock(BasicBlock b, boolean branch) {
|
||||
branch in [true, false] and
|
||||
conditionBlock.controls(b, branch.booleanXor(flipped))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if the guard `g` validates `node` upon evaluating to `branch`.
|
||||
*/
|
||||
signature predicate guardChecksSig(GuardNode g, Cfg::ControlFlowNode node, boolean branch);
|
||||
signature predicate guardChecksSig(GuardNode g, ControlFlowNode node, boolean branch);
|
||||
|
||||
/**
|
||||
* Provides a set of barrier nodes for a guard that validates a node.
|
||||
@@ -714,9 +670,7 @@ module BarrierGuard<guardChecksSig/3 guardChecks> {
|
||||
result = ParameterizedBarrierGuard<Unit, extendedGuardChecks/4>::getABarrierNode(_)
|
||||
}
|
||||
|
||||
private predicate extendedGuardChecks(
|
||||
GuardNode g, Cfg::ControlFlowNode node, boolean branch, Unit u
|
||||
) {
|
||||
private predicate extendedGuardChecks(GuardNode g, ControlFlowNode node, boolean branch, Unit u) {
|
||||
guardChecks(g, node, branch) and
|
||||
u = u
|
||||
}
|
||||
@@ -726,7 +680,7 @@ bindingset[this]
|
||||
private signature class ParamSig;
|
||||
|
||||
private module WithParam<ParamSig P> {
|
||||
signature predicate guardChecksSig(GuardNode g, Cfg::ControlFlowNode node, boolean branch, P param);
|
||||
signature predicate guardChecksSig(GuardNode g, ControlFlowNode node, boolean branch, P param);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -739,19 +693,10 @@ module ParameterizedBarrierGuard<ParamSig P, WithParam<P>::guardChecksSig/4 guar
|
||||
/** Gets a node that is safely guarded by the given guard check with parameter `param`. */
|
||||
overlay[global]
|
||||
ExprNode getABarrierNode(P param) {
|
||||
exists(GuardNode g, SsaImpl::EssaDefinition def, Cfg::ControlFlowNode node, boolean branch |
|
||||
SsaImpl::AdjacentUses::useOfDef(def, node) and
|
||||
exists(GuardNode g, EssaDefinition def, ControlFlowNode node, boolean branch |
|
||||
AdjacentUses::useOfDef(def, node) and
|
||||
guardChecks(g, node, branch, param) and
|
||||
SsaImpl::AdjacentUses::useOfDef(def, result.asCfgNode()) and
|
||||
// The protected use must be a different SSA position than the test
|
||||
// position itself: in the shared CFG the test expression's
|
||||
// canonical CFG node is the firstNode of its own true-branch BB
|
||||
// (`controlsBlock` is reflexive on dominance), and that canonical
|
||||
// node is also the SSA-use position on the def-use chain leading
|
||||
// to the actual branch uses. Without this guard, the test
|
||||
// position would be returned as a barrier and block flow before
|
||||
// it can reach the genuine uses in either branch.
|
||||
node != result.asCfgNode() and
|
||||
AdjacentUses::useOfDef(def, result.asCfgNode()) and
|
||||
g.controlsBlock(result.asCfgNode().getBasicBlock(), branch)
|
||||
)
|
||||
}
|
||||
@@ -767,7 +712,7 @@ module ExternalBarrierGuard {
|
||||
private import semmle.python.ApiGraphs
|
||||
|
||||
overlay[global]
|
||||
private predicate guardCheck(GuardNode g, Cfg::ControlFlowNode node, boolean branch, string kind) {
|
||||
private predicate guardCheck(GuardNode g, ControlFlowNode node, boolean branch, string kind) {
|
||||
exists(API::CallNode call, API::Node parameter |
|
||||
parameter = call.getAParameter() and
|
||||
parameter = ModelOutput::getABarrierGuardNode(kind, branch)
|
||||
@@ -803,10 +748,10 @@ newtype TContent =
|
||||
TSetElementContent() or
|
||||
/** An element of a tuple at a specific index. */
|
||||
TTupleElementContent(int index) {
|
||||
exists(any(Cfg::TupleNode tn).getElement(index))
|
||||
exists(any(TupleNode tn).getElement(index))
|
||||
or
|
||||
// Arguments can overflow and end up in the starred parameter tuple.
|
||||
exists(any(Cfg::CallNode cn).getArg(index))
|
||||
exists(any(CallNode cn).getArg(index))
|
||||
or
|
||||
// since flow summaries might use tuples, we ensure that we at least have valid
|
||||
// TTupleElementContent for the 0..7 (7 was picked to match `small_tuple` in
|
||||
@@ -823,14 +768,10 @@ newtype TContent =
|
||||
or
|
||||
// d["key"] = ...
|
||||
key =
|
||||
any(Cfg::SubscriptNode sub |
|
||||
sub.isStore()
|
||||
|
|
||||
sub.getIndex().getNode().(StringLiteral).getText()
|
||||
)
|
||||
any(SubscriptNode sub | sub.isStore() | sub.getIndex().getNode().(StringLiteral).getText())
|
||||
or
|
||||
// d.setdefault("key", ...)
|
||||
exists(Cfg::CallNode call | call.getFunction().(Cfg::AttrNode).getName() = "setdefault" |
|
||||
exists(CallNode call | call.getFunction().(AttrNode).getName() = "setdefault" |
|
||||
key = call.getArg(0).getNode().(StringLiteral).getText()
|
||||
)
|
||||
} or
|
||||
@@ -957,19 +898,78 @@ class CapturedVariableContent extends Content, TCapturedVariableContent {
|
||||
override string getMaDRepresentation() { none() }
|
||||
}
|
||||
|
||||
/**
|
||||
* An entity that represents a set of `Content`s.
|
||||
*
|
||||
* Most `ContentSet`s are singletons (i.e. they consist of a single `Content`),
|
||||
* but `AnyDictionaryElement` and `AnyTupleElement` act as wildcards on the
|
||||
* read side: a read at such a `ContentSet` matches any specific dictionary
|
||||
* key / tuple index store, as well as (for dictionaries) the
|
||||
* "unknown-bucket" Content `DictionaryElementAnyContent`.
|
||||
*
|
||||
* Keeping these as wildcard `ContentSet`s (rather than enumerating one
|
||||
* `ContentSet` per key/index) keeps the dataflow `readSetEx` relation small
|
||||
* when implicit reads are used (e.g. at sinks via `defaultImplicitTaintRead`).
|
||||
*/
|
||||
private newtype TContentSet =
|
||||
TSingletonContent(Content c) or
|
||||
TAnyTupleElement() or
|
||||
TAnyDictionaryElement() or
|
||||
TAnyTupleOrDictionaryElement()
|
||||
|
||||
/**
|
||||
* An entity that represents a set of `Content`s.
|
||||
*
|
||||
* The set may be interpreted differently depending on whether it is
|
||||
* stored into (`getAStoreContent`) or read from (`getAReadContent`).
|
||||
*/
|
||||
class ContentSet instanceof Content {
|
||||
class ContentSet extends TContentSet {
|
||||
/** Holds if this content set is the singleton `{c}`. */
|
||||
predicate isSingleton(Content c) { this = TSingletonContent(c) }
|
||||
|
||||
/** Holds if this content set is the wildcard for all tuple elements. */
|
||||
predicate isAnyTupleElement() { this = TAnyTupleElement() }
|
||||
|
||||
/** Holds if this content set is the wildcard for all dictionary elements. */
|
||||
predicate isAnyDictionaryElement() { this = TAnyDictionaryElement() }
|
||||
|
||||
/** Holds if this content set is the wildcard for all tuple elements or dictionary elements. */
|
||||
predicate isAnyTupleOrDictionaryElement() { this = TAnyTupleOrDictionaryElement() }
|
||||
|
||||
/** Gets a content that may be stored into when storing into this set. */
|
||||
Content getAStoreContent() { result = this }
|
||||
Content getAStoreContent() { this = TSingletonContent(result) }
|
||||
|
||||
/** Gets a content that may be read from when reading from this set. */
|
||||
Content getAReadContent() { result = this }
|
||||
Content getAReadContent() {
|
||||
this = TSingletonContent(result)
|
||||
or
|
||||
// Wildcard expansion: a read at "any tuple element" matches a store at any
|
||||
// specific tuple index. (Stores always target a specific index, so we don't
|
||||
// need a `TupleElementAnyContent` Content kind here.)
|
||||
this = TAnyTupleElement() and result instanceof TupleElementContent
|
||||
or
|
||||
this = TAnyDictionaryElement() and
|
||||
(result instanceof DictionaryElementContent or result instanceof DictionaryElementAnyContent)
|
||||
or
|
||||
this = TAnyTupleOrDictionaryElement() and
|
||||
(
|
||||
result instanceof TupleElementContent or
|
||||
result instanceof DictionaryElementContent or
|
||||
result instanceof DictionaryElementAnyContent
|
||||
)
|
||||
}
|
||||
|
||||
/** Gets a textual representation of this content set. */
|
||||
string toString() { result = super.toString() }
|
||||
string toString() {
|
||||
exists(Content c | this = TSingletonContent(c) | result = c.toString())
|
||||
or
|
||||
this = TAnyTupleElement() and result = "Any tuple element"
|
||||
or
|
||||
this = TAnyDictionaryElement() and result = "Any dictionary element"
|
||||
or
|
||||
this = TAnyTupleOrDictionaryElement() and result = "Any tuple or dictionary element"
|
||||
}
|
||||
}
|
||||
|
||||
/** Gets the singleton `ContentSet` wrapping the `Content` `c`. */
|
||||
ContentSet singleton(Content c) { result = TSingletonContent(c) }
|
||||
|
||||
@@ -66,21 +66,29 @@ module Input implements InputSig<Location, DataFlowImplSpecific::PythonDataFlow>
|
||||
}
|
||||
|
||||
string encodeContent(ContentSet cs, string arg) {
|
||||
cs = TListElementContent() and result = "ListElement" and arg = ""
|
||||
or
|
||||
cs = TSetElementContent() and result = "SetElement" and arg = ""
|
||||
or
|
||||
exists(int index |
|
||||
cs = TTupleElementContent(index) and result = "TupleElement" and arg = index.toString()
|
||||
exists(Content c | cs.isSingleton(c) |
|
||||
c = TListElementContent() and result = "ListElement" and arg = ""
|
||||
or
|
||||
c = TSetElementContent() and result = "SetElement" and arg = ""
|
||||
or
|
||||
exists(int index |
|
||||
c = TTupleElementContent(index) and result = "TupleElement" and arg = index.toString()
|
||||
)
|
||||
or
|
||||
exists(string key |
|
||||
c = TDictionaryElementContent(key) and result = "DictionaryElement" and arg = key
|
||||
)
|
||||
or
|
||||
c = TDictionaryElementAnyContent() and result = "DictionaryElementAny" and arg = ""
|
||||
or
|
||||
exists(string attr | c = TAttributeContent(attr) and result = "Attribute" and arg = attr)
|
||||
)
|
||||
or
|
||||
exists(string key |
|
||||
cs = TDictionaryElementContent(key) and result = "DictionaryElement" and arg = key
|
||||
)
|
||||
cs.isAnyTupleElement() and result = "AnyTupleElement" and arg = ""
|
||||
or
|
||||
cs = TDictionaryElementAnyContent() and result = "DictionaryElementAny" and arg = ""
|
||||
cs.isAnyDictionaryElement() and result = "AnyDictionaryElement" and arg = ""
|
||||
or
|
||||
exists(string attr | cs = TAttributeContent(attr) and result = "Attribute" and arg = attr)
|
||||
cs.isAnyTupleOrDictionaryElement() and result = "AnyTupleOrDictionaryElement" and arg = ""
|
||||
}
|
||||
|
||||
bindingset[token]
|
||||
@@ -139,27 +147,29 @@ module Private {
|
||||
predicate withContent = SC::withContent/1;
|
||||
|
||||
/** Gets a summary component that represents a list element. */
|
||||
SummaryComponent listElement() { result = content(any(ListElementContent c)) }
|
||||
SummaryComponent listElement() { result = content(singleton(any(ListElementContent c))) }
|
||||
|
||||
/** Gets a summary component that represents a set element. */
|
||||
SummaryComponent setElement() { result = content(any(SetElementContent c)) }
|
||||
SummaryComponent setElement() { result = content(singleton(any(SetElementContent c))) }
|
||||
|
||||
/** Gets a summary component that represents a tuple element. */
|
||||
SummaryComponent tupleElement(int index) {
|
||||
exists(TupleElementContent c | c.getIndex() = index and result = content(c))
|
||||
exists(TupleElementContent c | c.getIndex() = index and result = content(singleton(c)))
|
||||
}
|
||||
|
||||
/** Gets a summary component that represents a dictionary element. */
|
||||
SummaryComponent dictionaryElement(string key) {
|
||||
exists(DictionaryElementContent c | c.getKey() = key and result = content(c))
|
||||
exists(DictionaryElementContent c | c.getKey() = key and result = content(singleton(c)))
|
||||
}
|
||||
|
||||
/** Gets a summary component that represents a dictionary element at any key. */
|
||||
SummaryComponent dictionaryElementAny() { result = content(any(DictionaryElementAnyContent c)) }
|
||||
SummaryComponent dictionaryElementAny() {
|
||||
result = content(singleton(any(DictionaryElementAnyContent c)))
|
||||
}
|
||||
|
||||
/** Gets a summary component that represents an attribute element. */
|
||||
SummaryComponent attribute(string attr) {
|
||||
exists(AttributeContent c | c.getAttribute() = attr and result = content(c))
|
||||
exists(AttributeContent c | c.getAttribute() = attr and result = content(singleton(c)))
|
||||
}
|
||||
|
||||
/** Gets a summary component that represents the return value of a call. */
|
||||
|
||||
@@ -5,24 +5,11 @@
|
||||
*/
|
||||
|
||||
private import python
|
||||
private import semmle.python.controlflow.internal.Cfg as Cfg
|
||||
private import semmle.python.dataflow.new.internal.SsaImpl as SsaImpl
|
||||
private import semmle.python.dataflow.new.DataFlow
|
||||
private import semmle.python.dataflow.new.internal.ImportStar
|
||||
private import semmle.python.dataflow.new.TypeTracking
|
||||
private import semmle.python.dataflow.new.internal.DataFlowPrivate
|
||||
|
||||
/**
|
||||
* Holds if the name of `var` refers to a submodule of a package and `init` is
|
||||
* the `__init__` module of that package. Locally inlined replacement for the
|
||||
* legacy `SsaSource::init_module_submodule_defn` so that this module has no
|
||||
* direct dependency on `semmle.python.essa.SsaDefinitions`.
|
||||
*/
|
||||
private predicate initModuleSubmoduleDefn(GlobalVariable var, Module init) {
|
||||
init.isPackageInit() and
|
||||
exists(init.getPackage().getSubModule(var.getId())) and
|
||||
var.getScope() = init
|
||||
}
|
||||
private import semmle.python.essa.SsaDefinitions
|
||||
|
||||
/**
|
||||
* Python modules and the way imports are resolved are... complicated. Here's a crash course in how
|
||||
@@ -82,19 +69,13 @@ module ImportResolution {
|
||||
* Holds if there is an ESSA step from `defFrom` to `defTo`, which should be allowed
|
||||
* for import resolution.
|
||||
*/
|
||||
private predicate allowedEssaImportStep(
|
||||
SsaImpl::EssaDefinition defFrom, SsaImpl::EssaDefinition defTo
|
||||
) {
|
||||
private predicate allowedEssaImportStep(EssaDefinition defFrom, EssaDefinition defTo) {
|
||||
// to handle definitions guarded by if-then-else
|
||||
defFrom = defTo.(SsaImpl::PhiFunction).getAnInput()
|
||||
defFrom = defTo.(PhiFunction).getAnInput()
|
||||
or
|
||||
// to handle uncertain writes such as `from X import *`, which create an
|
||||
// uncertain SSA definition for every name in the importing scope. The
|
||||
// immediately preceding definition is still potentially the value of the
|
||||
// module export.
|
||||
SsaImpl::Ssa::uncertainWriteDefinitionInput(defTo, defFrom)
|
||||
// Note: legacy ESSA refinement-step (e.g. for `foo.bar = X`) is
|
||||
// not modelled in the new SSA beyond the cases handled above.
|
||||
// refined variable
|
||||
// example: https://github.com/nvbn/thefuck/blob/ceeaeab94b5df5a4fe9d94d61e4f6b0bbea96378/thefuck/utils.py#L25-L45
|
||||
defFrom = defTo.(EssaNodeRefinement).getInput().getDefinition()
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -111,32 +92,30 @@ module ImportResolution {
|
||||
// Definitions made inside `m` itself
|
||||
//
|
||||
// for code such as `foo = ...; foo.bar = ...` there will be TWO
|
||||
// SsaImpl::EssaDefinition/SsaImpl::EssaVariable. One for `foo = ...` (SsaImpl::AssignmentDefinition) and one
|
||||
// EssaDefinition/EssaVariable. One for `foo = ...` (AssignmentDefinition) and one
|
||||
// for `foo.bar = ...`. The one for `foo.bar = ...` (EssaNodeRefinement). The
|
||||
// EssaNodeRefinement is the one that will reach the end of the module (normal
|
||||
// exit).
|
||||
//
|
||||
// However, we cannot just use the EssaNodeRefinement as the `val`, because the
|
||||
// normal data-flow depends on use-use flow, and use-use flow targets CFG nodes not
|
||||
// EssaNodes. So we need to go back from the SsaImpl::EssaDefinition/SsaImpl::EssaVariable that
|
||||
// EssaNodes. So we need to go back from the EssaDefinition/EssaVariable that
|
||||
// reaches the end of the module, to the first definition of the variable, and then
|
||||
// track forwards using use-use flow to find a suitable CFG node that has flow into
|
||||
// it from use-use flow.
|
||||
exists(SsaImpl::EssaVariable lastUseVar, SsaImpl::EssaVariable firstDef |
|
||||
exists(EssaVariable lastUseVar, EssaVariable firstDef |
|
||||
lastUseVar.getName() = name and
|
||||
// we ignore special variable $ introduced by our analysis (not used for anything)
|
||||
// we ignore special variable * introduced by `from <pkg> import *` -- TODO: understand why we even have this?
|
||||
not name in ["$", "*"] and
|
||||
exists(Cfg::ControlFlowNode exit |
|
||||
exit.isNormalExit() and exit.getScope() = m and lastUseVar.getAUse() = exit
|
||||
) and
|
||||
lastUseVar.getAUse() = m.getANormalExit() and
|
||||
allowedEssaImportStep*(firstDef, lastUseVar) and
|
||||
not allowedEssaImportStep(_, firstDef)
|
||||
|
|
||||
not LocalFlow::defToFirstUse(firstDef, _) and
|
||||
val.asCfgNode() = firstDef.getDefinition().(SsaImpl::EssaNodeDefinition).getDefiningNode()
|
||||
val.asCfgNode() = firstDef.getDefinition().(EssaNodeDefinition).getDefiningNode()
|
||||
or
|
||||
exists(Cfg::ControlFlowNode mid, Cfg::ControlFlowNode end |
|
||||
exists(ControlFlowNode mid, ControlFlowNode end |
|
||||
LocalFlow::defToFirstUse(firstDef, mid) and
|
||||
LocalFlow::useToNextUse*(mid, end) and
|
||||
not LocalFlow::useToNextUse(end, _) and
|
||||
@@ -164,9 +143,9 @@ module ImportResolution {
|
||||
* handles simple cases where we can statically tell that this is the case.
|
||||
*/
|
||||
private predicate all_mentions_name(Module m, string name) {
|
||||
exists(Cfg::DefinitionNode def, Cfg::SequenceNode n |
|
||||
exists(DefinitionNode def, SequenceNode n |
|
||||
def.getValue() = n and
|
||||
def.(Cfg::NameNode).getId() = "__all__" and
|
||||
def.(NameNode).getId() = "__all__" and
|
||||
def.getScope() = m and
|
||||
any(StringLiteral s | s.getText() = name) = n.getAnElement().getNode()
|
||||
)
|
||||
@@ -179,20 +158,18 @@ module ImportResolution {
|
||||
*/
|
||||
private predicate no_or_complicated_all(Module m) {
|
||||
// No mention of `__all__` in the module
|
||||
not exists(Cfg::DefinitionNode def |
|
||||
def.getScope() = m and def.(Cfg::NameNode).getId() = "__all__"
|
||||
)
|
||||
not exists(DefinitionNode def | def.getScope() = m and def.(NameNode).getId() = "__all__")
|
||||
or
|
||||
// `__all__` is set to a non-sequence value
|
||||
exists(Cfg::DefinitionNode def |
|
||||
def.(Cfg::NameNode).getId() = "__all__" and
|
||||
exists(DefinitionNode def |
|
||||
def.(NameNode).getId() = "__all__" and
|
||||
def.getScope() = m and
|
||||
not def.getValue() instanceof Cfg::SequenceNode
|
||||
not def.getValue() instanceof SequenceNode
|
||||
)
|
||||
or
|
||||
// `__all__` is used in some way that doesn't involve storing a value in it. This usually means
|
||||
// it is being mutated through `append` or `extend`, which we don't handle.
|
||||
exists(Cfg::NameNode n | n.getId() = "__all__" and n.getScope() = m and n.isLoad())
|
||||
exists(NameNode n | n.getId() = "__all__" and n.getScope() = m and n.isLoad())
|
||||
}
|
||||
|
||||
private predicate potential_module_export(Module m, string name) {
|
||||
@@ -200,7 +177,7 @@ module ImportResolution {
|
||||
or
|
||||
no_or_complicated_all(m) and
|
||||
(
|
||||
exists(Cfg::NameNode n | n.getId() = name and n.getScope() = m and name.charAt(0) != "_")
|
||||
exists(NameNode n | n.getId() = name and n.getScope() = m and name.charAt(0) != "_")
|
||||
or
|
||||
exists(Alias a | a.getAsname().(Name).getId() = name and a.getValue().getScope() = m)
|
||||
)
|
||||
@@ -230,12 +207,12 @@ module ImportResolution {
|
||||
|
||||
/** Gets a module that may have been added to `sys.modules`. */
|
||||
private Module sys_modules_module_with_name(string name) {
|
||||
exists(Cfg::ControlFlowNode n, DataFlow::Node mod |
|
||||
exists(Cfg::SubscriptNode sub |
|
||||
exists(ControlFlowNode n, DataFlow::Node mod |
|
||||
exists(SubscriptNode sub |
|
||||
sub.getObject() = sys_modules_reference().asCfgNode() and
|
||||
sub.getIndex() = n and
|
||||
n.getNode().(StringLiteral).getText() = name and
|
||||
sub.(Cfg::DefinitionNode).getValue() = mod.asCfgNode() and
|
||||
sub.(DefinitionNode).getValue() = mod.asCfgNode() and
|
||||
mod = getModuleReference(result)
|
||||
)
|
||||
)
|
||||
@@ -347,11 +324,11 @@ module ImportResolution {
|
||||
// name as a submodule, we always consider that this attribute _could_ be a
|
||||
// reference to the submodule, even if we don't know that the submodule has been
|
||||
// imported yet.
|
||||
exists(string submodule, Module package, SsaImpl::EssaVariable var |
|
||||
exists(string submodule, Module package, EssaVariable var |
|
||||
submodule = var.getName() and
|
||||
initModuleSubmoduleDefn(var.getSourceVariable().getVariable(), package) and
|
||||
SsaSource::init_module_submodule_defn(var.getSourceVariable(), package.getEntryNode()) and
|
||||
m = getModuleFromName(package.getPackageName() + "." + submodule) and
|
||||
result.asCfgNode() = var.getDefinition().(SsaImpl::EssaNodeDefinition).getDefiningNode()
|
||||
result.asCfgNode() = var.getDefinition().(EssaNodeDefinition).getDefiningNode()
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@ overlay[local]
|
||||
module;
|
||||
|
||||
private import python
|
||||
private import semmle.python.controlflow.internal.Cfg as Cfg
|
||||
private import semmle.python.dataflow.new.internal.Builtins
|
||||
private import semmle.python.dataflow.new.internal.ImportResolution
|
||||
private import semmle.python.dataflow.new.DataFlow
|
||||
@@ -16,7 +15,7 @@ module ImportStar {
|
||||
*/
|
||||
overlay[local]
|
||||
cached
|
||||
predicate namePossiblyDefinedInImportStar(Cfg::NameNode n, string name, Scope s) {
|
||||
predicate namePossiblyDefinedInImportStar(NameNode n, string name, Scope s) {
|
||||
n.isLoad() and
|
||||
name = n.getId() and
|
||||
s = n.getScope().getEnclosingScope*() and
|
||||
@@ -53,7 +52,7 @@ module ImportStar {
|
||||
/** Holds if a global variable called `name` is assigned a value in the module `m`. */
|
||||
cached
|
||||
predicate globalNameDefinedInModule(string name, Module m) {
|
||||
exists(Cfg::NameNode n |
|
||||
exists(NameNode n |
|
||||
not exists(LocalVariable v | n.defines(v)) and
|
||||
n.isStore() and
|
||||
name = n.getId() and
|
||||
@@ -67,7 +66,7 @@ module ImportStar {
|
||||
*/
|
||||
overlay[global]
|
||||
cached
|
||||
predicate importStarResolvesTo(Cfg::NameNode n, Module m) {
|
||||
predicate importStarResolvesTo(NameNode n, Module m) {
|
||||
m = getStarImported+(n.getEnclosingModule()) and
|
||||
globalNameDefinedInModule(n.getId(), m) and
|
||||
not isDefinedLocally(n.getNode())
|
||||
@@ -100,7 +99,7 @@ module ImportStar {
|
||||
*/
|
||||
overlay[local]
|
||||
cached
|
||||
Cfg::ControlFlowNode potentialImportStarBase(Scope s) {
|
||||
result = any(Cfg::ImportStarNode n | n.getScope() = s).getModule()
|
||||
ControlFlowNode potentialImportStarBase(Scope s) {
|
||||
result = any(ImportStarNode n | n.getScope() = s).getModule()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -170,8 +170,6 @@ overlay[local]
|
||||
module;
|
||||
|
||||
private import python
|
||||
private import semmle.python.controlflow.internal.Cfg as Cfg
|
||||
private import semmle.python.dataflow.new.internal.SsaImpl as SsaImpl
|
||||
private import DataFlowPublic
|
||||
|
||||
/**
|
||||
@@ -180,7 +178,7 @@ private import DataFlowPublic
|
||||
* This class abstracts away the differing representations of comprehensions and
|
||||
* for statements.
|
||||
*/
|
||||
class ForTarget extends Cfg::ControlFlowNode {
|
||||
class ForTarget extends ControlFlowNode {
|
||||
Expr source;
|
||||
|
||||
ForTarget() {
|
||||
@@ -200,7 +198,7 @@ class ForTarget extends Cfg::ControlFlowNode {
|
||||
}
|
||||
|
||||
/** The LHS of an assignment, it also records the assigned value. */
|
||||
class AssignmentTarget extends Cfg::ControlFlowNode {
|
||||
class AssignmentTarget extends ControlFlowNode {
|
||||
Expr value;
|
||||
|
||||
AssignmentTarget() {
|
||||
@@ -211,7 +209,7 @@ class AssignmentTarget extends Cfg::ControlFlowNode {
|
||||
}
|
||||
|
||||
/** A direct (or top-level) target of an unpacking assignment. */
|
||||
class UnpackingAssignmentDirectTarget extends Cfg::ControlFlowNode instanceof Cfg::SequenceNode {
|
||||
class UnpackingAssignmentDirectTarget extends ControlFlowNode instanceof SequenceNode {
|
||||
Expr value;
|
||||
|
||||
UnpackingAssignmentDirectTarget() {
|
||||
@@ -224,7 +222,7 @@ class UnpackingAssignmentDirectTarget extends Cfg::ControlFlowNode instanceof Cf
|
||||
}
|
||||
|
||||
/** A (possibly recursive) target of an unpacking assignment. */
|
||||
class UnpackingAssignmentTarget extends Cfg::ControlFlowNode {
|
||||
class UnpackingAssignmentTarget extends ControlFlowNode {
|
||||
UnpackingAssignmentTarget() {
|
||||
this instanceof UnpackingAssignmentDirectTarget
|
||||
or
|
||||
@@ -233,11 +231,10 @@ class UnpackingAssignmentTarget extends Cfg::ControlFlowNode {
|
||||
}
|
||||
|
||||
/** A (possibly recursive) target of an unpacking assignment which is also a sequence. */
|
||||
class UnpackingAssignmentSequenceTarget extends UnpackingAssignmentTarget instanceof Cfg::SequenceNode
|
||||
{
|
||||
Cfg::ControlFlowNode getElement(int i) { result = super.getElement(i) }
|
||||
class UnpackingAssignmentSequenceTarget extends UnpackingAssignmentTarget instanceof SequenceNode {
|
||||
ControlFlowNode getElement(int i) { result = super.getElement(i) }
|
||||
|
||||
Cfg::ControlFlowNode getAnElement() { result = this.getElement(_) }
|
||||
ControlFlowNode getAnElement() { result = this.getElement(_) }
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -258,7 +255,7 @@ predicate iterableUnpackingAssignmentFlowStep(Node nodeFrom, Node nodeTo) {
|
||||
predicate iterableUnpackingForReadStep(CfgNode nodeFrom, Content c, Node nodeTo) {
|
||||
exists(ForTarget target |
|
||||
nodeFrom.getNode().getNode() = target.getSource() and
|
||||
target instanceof Cfg::SequenceNode and
|
||||
target instanceof SequenceNode and
|
||||
nodeTo = TIterableSequenceNode(target)
|
||||
) and
|
||||
(
|
||||
@@ -326,11 +323,11 @@ predicate iterableUnpackingConvertingStoreStep(Node nodeFrom, Content c, Node no
|
||||
*/
|
||||
predicate iterableUnpackingElementReadStep(Node nodeFrom, Content c, Node nodeTo) {
|
||||
exists(
|
||||
UnpackingAssignmentSequenceTarget target, int index, Cfg::ControlFlowNode element, int starIndex
|
||||
UnpackingAssignmentSequenceTarget target, int index, ControlFlowNode element, int starIndex
|
||||
|
|
||||
target.getElement(starIndex) instanceof Cfg::StarredNode
|
||||
target.getElement(starIndex) instanceof StarredNode
|
||||
or
|
||||
not exists(target.getAnElement().(Cfg::StarredNode)) and
|
||||
not exists(target.getAnElement().(StarredNode)) and
|
||||
starIndex = -1
|
||||
|
|
||||
nodeFrom.(CfgNode).getNode() = target and
|
||||
@@ -345,18 +342,18 @@ predicate iterableUnpackingElementReadStep(Node nodeFrom, Content c, Node nodeTo
|
||||
else c.(TupleElementContent).getIndex() >= index - 1
|
||||
) and
|
||||
(
|
||||
if element instanceof Cfg::SequenceNode
|
||||
if element instanceof SequenceNode
|
||||
then
|
||||
// Step 5b
|
||||
nodeTo = TIterableSequenceNode(element)
|
||||
else
|
||||
if element instanceof Cfg::StarredNode
|
||||
if element instanceof StarredNode
|
||||
then
|
||||
// Step 5c
|
||||
nodeTo = TIterableElementNode(element)
|
||||
else
|
||||
// Step 5a
|
||||
exists(SsaImpl::MultiAssignmentDefinition mad | element = mad.getDefiningNode() |
|
||||
exists(MultiAssignmentDefinition mad | element = mad.getDefiningNode() |
|
||||
nodeTo.(CfgNode).getNode() = element
|
||||
)
|
||||
)
|
||||
@@ -369,7 +366,7 @@ predicate iterableUnpackingElementReadStep(Node nodeFrom, Content c, Node nodeTo
|
||||
* content type `ListElementContent`.
|
||||
*/
|
||||
predicate iterableUnpackingStarredElementStoreStep(Node nodeFrom, Content c, Node nodeTo) {
|
||||
exists(Cfg::ControlFlowNode starred, SsaImpl::MultiAssignmentDefinition mad |
|
||||
exists(ControlFlowNode starred, MultiAssignmentDefinition mad |
|
||||
starred.getNode() instanceof Starred and
|
||||
starred = mad.getDefiningNode()
|
||||
|
|
||||
|
||||
@@ -9,7 +9,6 @@ overlay[local]
|
||||
module;
|
||||
|
||||
private import python
|
||||
private import semmle.python.controlflow.internal.Cfg as Cfg
|
||||
import DataFlowPublic
|
||||
private import DataFlowPrivate
|
||||
private import semmle.python.internal.CachedStages
|
||||
@@ -315,7 +314,7 @@ private module Cached {
|
||||
*/
|
||||
cached
|
||||
predicate subscript(LocalSourceNode node, CfgNode subscript, CfgNode index) {
|
||||
exists(CfgNode seq, Cfg::SubscriptNode subscriptNode | subscriptNode = subscript.getNode() |
|
||||
exists(CfgNode seq, SubscriptNode subscriptNode | subscriptNode = subscript.getNode() |
|
||||
node.flowsTo(seq) and
|
||||
seq.getNode() = subscriptNode.getObject() and
|
||||
index.getNode() = subscriptNode.getIndex()
|
||||
|
||||
@@ -91,7 +91,9 @@ predicate matchAsFlowStep(Node nodeFrom, Node nodeTo) {
|
||||
or
|
||||
// the interior pattern flows to the alias
|
||||
nodeFrom.(CfgNode).getNode().getNode() = subject.getPattern() and
|
||||
nodeTo.(CfgNode).getNode().getNode() = alias
|
||||
exists(PatternAliasDefinition pad | pad.getDefiningNode().getNode() = alias |
|
||||
nodeTo.(CfgNode).getNode() = pad.getDefiningNode()
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -122,9 +124,11 @@ predicate matchLiteralFlowStep(Node nodeFrom, Node nodeTo) {
|
||||
* syntax (toplevel): `case var:`
|
||||
*/
|
||||
predicate matchCaptureFlowStep(Node nodeFrom, Node nodeTo) {
|
||||
exists(MatchCapturePattern capture |
|
||||
exists(MatchCapturePattern capture, Name var | capture.getVariable() = var |
|
||||
nodeFrom.(CfgNode).getNode().getNode() = capture and
|
||||
nodeTo.(CfgNode).getNode().getNode() = capture.getVariable()
|
||||
exists(PatternCaptureDefinition pcd | pcd.getDefiningNode().getNode() = var |
|
||||
nodeTo.(CfgNode).getNode() = pcd.getDefiningNode()
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,547 +0,0 @@
|
||||
/**
|
||||
* Provides the Python SSA implementation built on the new (shared) CFG.
|
||||
*
|
||||
* Mirrors the Java SSA adapter at
|
||||
* `java/ql/lib/semmle/code/java/dataflow/internal/SsaImpl.qll`:
|
||||
* an `InputSig` is defined in terms of positional `(BasicBlock, int)`
|
||||
* variable references, and the shared
|
||||
* `codeql.ssa.Ssa::Make<Location, Cfg, Input>` module is then
|
||||
* instantiated.
|
||||
*
|
||||
* `SourceVariable` is the AST-level `Py::Variable`. Variable references
|
||||
* are looked up via the CFG facade's `NameNode.defines`/`uses`/`deletes`
|
||||
* predicates, which themselves are one-line bridges to AST-level
|
||||
* `Name.defines`/`uses`/`deletes`.
|
||||
*
|
||||
* Implicit-entry definitions are inserted for:
|
||||
* - non-local / global / builtin variables that are read in the scope
|
||||
* but never assigned (no enclosing CFG node defines them),
|
||||
* - captured variables (variables defined in an enclosing scope that
|
||||
* are read inside the scope), and
|
||||
* - parameters, but only if the corresponding parameter name is *not*
|
||||
* itself a CFG node. With the C#-style parameter wiring already
|
||||
* installed in `AstNodeImpl.qll`, parameter names *are* CFG nodes,
|
||||
* so the regular `variableWrite` path handles them — no `i = -1`
|
||||
* entry is needed for ordinary parameters.
|
||||
*/
|
||||
overlay[local?]
|
||||
module;
|
||||
|
||||
private import python as Py
|
||||
private import semmle.python.controlflow.internal.AstNodeImpl as CfgImpl
|
||||
private import semmle.python.controlflow.internal.Cfg as Cfg
|
||||
private import codeql.ssa.Ssa as SsaImplCommon
|
||||
private import codeql.controlflow.BasicBlock as BB
|
||||
|
||||
/**
|
||||
* Adapts the Python `Cfg` facade to the shared SSA library's `CfgSig`.
|
||||
* All members are inherited from `Cfg::ControlFlowNode` and
|
||||
* `Cfg::BasicBlock`.
|
||||
*/
|
||||
private module CfgForSsa implements BB::CfgSig<Py::Location> {
|
||||
class ControlFlowNode = CfgImpl::ControlFlowNode;
|
||||
|
||||
class BasicBlock = CfgImpl::BasicBlock;
|
||||
|
||||
class EntryBasicBlock = CfgImpl::Cfg::EntryBasicBlock;
|
||||
|
||||
predicate dominatingEdge = CfgImpl::Cfg::dominatingEdge/2;
|
||||
}
|
||||
|
||||
/**
|
||||
* A source variable for SSA, wrapping a Python AST `Variable`.
|
||||
*
|
||||
* We only track variables that are read at least once in their scope —
|
||||
* tracking write-only variables would be unnecessary work — *except*
|
||||
* for module-scope globals, where the "read" can be external (e.g.
|
||||
* `import mymodule; mymodule.x`). Such globals are tracked
|
||||
* unconditionally so that import-resolution can find their defining
|
||||
* write.
|
||||
*/
|
||||
private newtype TSsaSourceVariable =
|
||||
TPyVar(Py::Variable v) {
|
||||
// Has a use somewhere — read-relevant for SSA.
|
||||
exists(Cfg::NameNode n | n.uses(v))
|
||||
or
|
||||
// Or has a deletion (treated as a write that destroys the value).
|
||||
exists(Cfg::NameNode n | n.deletes(v))
|
||||
or
|
||||
// Or is a module-scope global written in this module — must be
|
||||
// tracked even if never read locally, because importers may read
|
||||
// it as an attribute on the module object.
|
||||
v.getScope() instanceof Py::Module and
|
||||
exists(Cfg::NameNode n | n.defines(v))
|
||||
or
|
||||
// Or is a parameter — parameters must always have a
|
||||
// `ParameterDefinition` for dataflow argument-routing to work,
|
||||
// even if the parameter is never read in its scope. Mirrors
|
||||
// legacy ESSA's `ParameterDefinition` (which fired for every
|
||||
// parameter binding regardless of liveness).
|
||||
exists(Py::Parameter p | p.asName() = v.getAStore())
|
||||
}
|
||||
|
||||
/**
|
||||
* A source variable for SSA, wrapping a Python AST `Variable`.
|
||||
*/
|
||||
class SsaSourceVariable extends TSsaSourceVariable {
|
||||
/** Gets the underlying Python AST variable. */
|
||||
Py::Variable getVariable() { this = TPyVar(result) }
|
||||
|
||||
/** Gets the (textual) name of this variable. */
|
||||
string getName() { result = this.getVariable().getId() }
|
||||
|
||||
/** Gets a textual representation of this source variable. */
|
||||
string toString() { result = this.getVariable().toString() }
|
||||
|
||||
/** Gets the location of this source variable. */
|
||||
Py::Location getLocation() { result = this.getVariable().getScope().getLocation() }
|
||||
|
||||
/** Gets the scope in which this variable lives. */
|
||||
Py::Scope getScope() { result = this.getVariable().getScope() }
|
||||
|
||||
/**
|
||||
* Gets a use of this variable as it appears in the source — a `NameNode`
|
||||
* that loads or deletes the variable. Mirrors legacy
|
||||
* `SsaSourceVariable.getASourceUse()`.
|
||||
*/
|
||||
Cfg::ControlFlowNode getASourceUse() {
|
||||
exists(Cfg::NameNode n | result = n |
|
||||
n.uses(this.getVariable()) or n.deletes(this.getVariable())
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an implicit use of this variable. The new SSA does not have
|
||||
* implicit-use refinements, but we keep this for API parity — every
|
||||
* normal-exit of the variable's scope counts as a sink, ensuring
|
||||
* variables stay live to scope exit for taint-tracking.
|
||||
*/
|
||||
Cfg::ControlFlowNode getAnImplicitUse() {
|
||||
result.isNormalExit() and result.getScope() = this.getScope()
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a use of this variable — either an explicit source use or an
|
||||
* implicit use at scope exit. Mirrors legacy `SsaSourceVariable.getAUse()`.
|
||||
*/
|
||||
Cfg::ControlFlowNode getAUse() {
|
||||
result = this.getASourceUse() or result = this.getAnImplicitUse()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `v` is a non-local read in scope `s`, in the sense that `s`
|
||||
* uses `v` but does not write it within `s`. This includes globals,
|
||||
* builtins, and variables captured from an enclosing function scope.
|
||||
*
|
||||
* The `Py::Variable` `v` lives in some defining scope (the module for
|
||||
* globals, an outer function for closures, etc.); the reading scope
|
||||
* `s` is the scope where the use of `v` occurs.
|
||||
*/
|
||||
private predicate nonLocalReadIn(Py::Variable v, Py::Scope s) {
|
||||
exists(Cfg::NameNode n |
|
||||
n.uses(v) and
|
||||
n.getScope() = s and
|
||||
not exists(Cfg::NameNode def | def.defines(v) and def.getScope() = s)
|
||||
) and
|
||||
// Match legacy ESSA: only create entry defs for variables that have
|
||||
// at least one defining store somewhere — otherwise the entry def
|
||||
// represents "nothing reaches here", which is the default anyway and
|
||||
// introduces no useful flow. (Legacy's `ModuleVariable` required a
|
||||
// store; this is the closure-aware generalisation.)
|
||||
exists(Cfg::NameNode store | store.defines(v))
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `bb` is the entry basic block of a scope where `v` should
|
||||
* have an implicit entry definition. This covers:
|
||||
* - non-local / global / builtin variables read in `s`, and
|
||||
* - captured variables (defined in an enclosing scope but read in `s`).
|
||||
*
|
||||
* Each reading scope gets its own entry def, so a closure variable can
|
||||
* have multiple entry defs across all functions/methods that read it.
|
||||
*
|
||||
* Parameters are *not* included: their bound `Name` is itself a CFG
|
||||
* node (per the C#-style parameter wiring), so `variableWrite` fires at
|
||||
* the parameter's natural CFG index.
|
||||
*/
|
||||
private predicate hasEntryDefIn(SsaSourceVariable v, CfgImpl::BasicBlock bb) {
|
||||
exists(Py::Scope s |
|
||||
nonLocalReadIn(v.getVariable(), s) and
|
||||
bb = entryBlock(s)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the entry basic block of scope `s`, where implicit entry
|
||||
* definitions are placed (at synthetic index `-1`).
|
||||
*/
|
||||
private CfgImpl::BasicBlock entryBlock(Py::Scope s) {
|
||||
exists(CfgImpl::ControlFlowNode entry |
|
||||
entry instanceof CfgImpl::ControlFlow::EntryNode and
|
||||
entry.getEnclosingCallable().asScope() = s and
|
||||
result = entry.getBasicBlock()
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* The SSA `InputSig` for Python. References are positional
|
||||
* `(BasicBlock, int)` pairs into the new CFG.
|
||||
*/
|
||||
private module SsaImplInput implements SsaImplCommon::InputSig<Py::Location, CfgImpl::BasicBlock> {
|
||||
class SourceVariable = SsaSourceVariable;
|
||||
|
||||
predicate variableWrite(CfgImpl::BasicBlock bb, int i, SourceVariable v, boolean certain) {
|
||||
// Explicit binding at a CFG node — includes assignments,
|
||||
// parameter Names (wired in via the C# pattern), exception-handler
|
||||
// `as`-bindings, import aliases, and match-pattern captures.
|
||||
exists(Cfg::NameNode n |
|
||||
bb.getNode(i) = n and
|
||||
n.defines(v.getVariable()) and
|
||||
certain = true
|
||||
)
|
||||
or
|
||||
// `del x` — removes the binding. Modelled as a certain write that
|
||||
// makes any subsequent read invalid.
|
||||
exists(Cfg::NameNode n |
|
||||
bb.getNode(i) = n and
|
||||
n.deletes(v.getVariable()) and
|
||||
certain = true
|
||||
)
|
||||
or
|
||||
// Implicit entry definition for non-local / captured / global /
|
||||
// builtin variables read in some scope. Each reading scope's entry
|
||||
// block gets one such write, allowing closures: e.g. when `x` is a
|
||||
// parameter of an outer function and read inside a nested
|
||||
// function, both scopes get entry defs for `x`.
|
||||
hasEntryDefIn(v, bb) and
|
||||
i = -1 and
|
||||
certain = true
|
||||
or
|
||||
// `from X import *` — possibly rebinds every name in the importing
|
||||
// scope. Modelled as an uncertain write at the import-star's CFG
|
||||
// position for every variable that lives in (or is referenced
|
||||
// from) the same scope as the import-star. Mirrors legacy ESSA's
|
||||
// `ImportStarRefinement` (see `essa/SsaDefinitions.qll`'s
|
||||
// `import_star_refinement` predicate). The write is uncertain so
|
||||
// that prior definitions of the variable remain available — the
|
||||
// shared-SSA `SsaUncertainWrite` merges the new value with the
|
||||
// immediately preceding definition.
|
||||
exists(Cfg::ImportStarNode imp |
|
||||
bb.getNode(i) = imp and
|
||||
certain = false and
|
||||
(
|
||||
v.getVariable().getScope() = imp.getScope()
|
||||
or
|
||||
// Variable is defined in some other scope but referenced in
|
||||
// the same scope as the import-star (matches legacy clause 2:
|
||||
// `other.uses(v) and def.getScope() = other.getScope()`).
|
||||
exists(Cfg::NameNode other |
|
||||
other.uses(v.getVariable()) and
|
||||
imp.getScope() = other.getScope()
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
predicate variableRead(CfgImpl::BasicBlock bb, int i, SourceVariable v, boolean certain) {
|
||||
// Explicit source use — a `Name` load or a `del x` of the variable.
|
||||
exists(Cfg::NameNode n |
|
||||
bb.getNode(i) = n and
|
||||
n.uses(v.getVariable()) and
|
||||
certain = true
|
||||
)
|
||||
or
|
||||
// Synthetic use at the normal exit of the variable's defining scope.
|
||||
// This keeps every variable live to scope exit so that callers (e.g.
|
||||
// `module_export` in ImportResolution.qll, or taint-tracking pass-through
|
||||
// through unread locals) can ask "which definition reaches end of
|
||||
// scope?". Mirrors legacy ESSA's `SsaSourceVariable.getAUse()` which
|
||||
// included `getScope().getANormalExit()`.
|
||||
exists(Cfg::ControlFlowNode exit |
|
||||
exit.isNormalExit() and
|
||||
exit.getScope() = v.getVariable().getScope() and
|
||||
bb.getNode(i) = exit and
|
||||
certain = true
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The shared SSA instantiation for Python.
|
||||
*
|
||||
* Members:
|
||||
* - `Definition` — the union of explicit, uncertain, and phi definitions
|
||||
* - `WriteDefinition`, `UncertainWriteDefinition`, `PhiNode`
|
||||
* - the standard SSA predicates (`getAUse`, `getAnUltimateDefinition`, ...).
|
||||
*/
|
||||
module Ssa = SsaImplCommon::Make<Py::Location, CfgForSsa, SsaImplInput>;
|
||||
|
||||
final class Definition = Ssa::Definition;
|
||||
|
||||
final class WriteDefinition = Ssa::WriteDefinition;
|
||||
|
||||
final class UncertainWriteDefinition = Ssa::UncertainWriteDefinition;
|
||||
|
||||
final class PhiNode = Ssa::PhiNode;
|
||||
|
||||
// ===========================================================================
|
||||
// ESSA-shaped adapter layer
|
||||
//
|
||||
// The dataflow library (`python/ql/lib/semmle/python/dataflow/new/`) and
|
||||
// related modules (`ApiGraphs.qll`, etc.) consume the legacy ESSA API
|
||||
// (`EssaVariable`, `EssaDefinition`, `AssignmentDefinition`,
|
||||
// `ScopeEntryDefinition`, `ParameterDefinition`, `WithDefinition`,
|
||||
// `PhiFunction`, plus the `AdjacentUses` module). To migrate them off
|
||||
// the legacy CFG, we expose the same API surface on top of the
|
||||
// shared SSA built above.
|
||||
//
|
||||
// This adapter is intentionally narrow: it covers only the predicates
|
||||
// that new dataflow consumes. The richer legacy ESSA — refinement
|
||||
// nodes, attribute refinements, edge refinements — stays available
|
||||
// via `semmle.python.essa.Essa` for points-to / legacy code.
|
||||
// ===========================================================================
|
||||
/**
|
||||
* Gets the CFG node at which a write definition's binding takes place.
|
||||
*
|
||||
* For ordinary writes (assignment, deletion, parameter) this is the
|
||||
* canonical CFG node of the bound Name. For implicit entry definitions
|
||||
* (synthesised at position `-1` of a scope's entry BB) this is the
|
||||
* scope's entry node.
|
||||
*/
|
||||
private Cfg::ControlFlowNode writeDefNode(Ssa::WriteDefinition def) {
|
||||
exists(CfgImpl::BasicBlock bb, int i | def.definesAt(_, bb, i) |
|
||||
i >= 0 and result = bb.getNode(i)
|
||||
or
|
||||
i = -1 and result = bb.getNode(0)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* A write definition whose binding has a corresponding CFG node — i.e.
|
||||
* everything that's not a phi node. Mirrors legacy ESSA's
|
||||
* `EssaNodeDefinition`.
|
||||
*/
|
||||
class EssaNodeDefinition extends Ssa::WriteDefinition {
|
||||
/** Gets the CFG node where this definition's binding takes place. */
|
||||
Cfg::ControlFlowNode getDefiningNode() { result = writeDefNode(this) }
|
||||
|
||||
/** Gets the variable defined here (legacy name). */
|
||||
SsaSourceVariable getVariable() { result = this.getSourceVariable() }
|
||||
|
||||
/** Gets the enclosing scope. */
|
||||
Py::Scope getScope() {
|
||||
exists(Cfg::ControlFlowNode n | n = this.getDefiningNode() | result = n.getScope())
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if this definition defines source variable `v` at CFG node
|
||||
* `defNode`. Flatter form of `getSourceVariable()` +
|
||||
* `getDefiningNode()`, matching legacy ESSA's `definedBy`.
|
||||
*/
|
||||
predicate definedBy(SsaSourceVariable v, Cfg::ControlFlowNode defNode) {
|
||||
v = this.getSourceVariable() and defNode = this.getDefiningNode()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An assignment definition: any binding where the value being assigned
|
||||
* is statically known via `Cfg::DefinitionNode.getValue()`. Includes
|
||||
* plain assignments, walrus, annotated assignments, augmented
|
||||
* assignments, import aliases (`import x` / `from m import x [as y]`),
|
||||
* `with ... as x`, and for-target bindings (where `getValue()` returns
|
||||
* the iter expression's CFG node). Excludes parameter bindings —
|
||||
* those are modelled by `ParameterDefinition`.
|
||||
*/
|
||||
class AssignmentDefinition extends EssaNodeDefinition {
|
||||
AssignmentDefinition() {
|
||||
exists(Cfg::NameNode n | n = this.getDefiningNode() |
|
||||
exists(n.(Cfg::DefinitionNode).getValue()) and
|
||||
not n.(Cfg::ControlFlowNode).isParameter()
|
||||
)
|
||||
}
|
||||
|
||||
/** Gets the CFG node for the value being assigned, if statically known. */
|
||||
Cfg::ControlFlowNode getValue() {
|
||||
result = this.getDefiningNode().(Cfg::DefinitionNode).getValue()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A parameter definition — the binding of a parameter name in a
|
||||
* function's scope.
|
||||
*/
|
||||
class ParameterDefinition extends EssaNodeDefinition {
|
||||
ParameterDefinition() { this.getDefiningNode().isParameter() }
|
||||
|
||||
/** Gets the AST `Parameter` (a `Py::Name` in param context). */
|
||||
Py::Name getParameter() { result = this.getDefiningNode().getNode() }
|
||||
}
|
||||
|
||||
/**
|
||||
* A definition introduced by a `with ... as x:` clause.
|
||||
*/
|
||||
class WithDefinition extends EssaNodeDefinition {
|
||||
WithDefinition() {
|
||||
exists(Cfg::NameNode n, Py::With w |
|
||||
n = this.getDefiningNode() and
|
||||
w.getOptionalVars() = n.getNode()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An assignment where the LHS is a tuple/list and the RHS is unpacked:
|
||||
* `a, b = (1, 2)` or `a, *rest = xs`. The SSA def lives at the inner
|
||||
* `Name` CFG node, but for IterableUnpacking integration we expose
|
||||
* the enclosing `StarredNode` as the `getDefiningNode()` for `*rest`
|
||||
* patterns — mirroring legacy ESSA's `multi_assignment_definition`,
|
||||
* which placed the def at the StarredNode CFG node.
|
||||
*/
|
||||
class MultiAssignmentDefinition extends EssaNodeDefinition {
|
||||
MultiAssignmentDefinition() {
|
||||
exists(Cfg::NameNode n | n = super.getDefiningNode() |
|
||||
exists(Py::Assign a, Py::Expr lhs |
|
||||
a.getATarget() = lhs and
|
||||
(lhs instanceof Py::Tuple or lhs instanceof Py::List) and
|
||||
lhs.getASubExpression+() = n.getNode()
|
||||
)
|
||||
or
|
||||
// For-loop with tuple/list target: `for a, b in xs:` —
|
||||
// tuple-unpacking semantics applies to the for-target.
|
||||
exists(Py::For f, Py::Expr lhs |
|
||||
f.getTarget() = lhs and
|
||||
(lhs instanceof Py::Tuple or lhs instanceof Py::List) and
|
||||
lhs.getASubExpression+() = n.getNode()
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
override Cfg::ControlFlowNode getDefiningNode() {
|
||||
// Default: the underlying `Name` CFG node (where the SSA def lives).
|
||||
not exists(Cfg::StarredNode s |
|
||||
s.getNode().(Py::Starred).getValue() = super.getDefiningNode().getNode()
|
||||
) and
|
||||
result = super.getDefiningNode()
|
||||
or
|
||||
// Exception: for `*rest`, expose the enclosing `Starred` CFG node
|
||||
// so that `IterableUnpacking::iterableUnpackingStarredElementStoreStep`
|
||||
// can attach the rest-list to it.
|
||||
exists(Cfg::StarredNode s |
|
||||
s.getNode().(Py::Starred).getValue() = super.getDefiningNode().getNode()
|
||||
|
|
||||
result = s
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An implicit entry definition for a non-local / captured / global /
|
||||
* builtin variable read in a scope but not defined there.
|
||||
*
|
||||
* Inherits from `EssaNodeDefinition` and exposes the scope's entry node
|
||||
* as its defining node (matching legacy ESSA semantics).
|
||||
*/
|
||||
class ScopeEntryDefinition extends EssaNodeDefinition {
|
||||
ScopeEntryDefinition() {
|
||||
exists(CfgImpl::BasicBlock bb |
|
||||
this.definesAt(_, bb, -1) and
|
||||
bb instanceof CfgImpl::Cfg::EntryBasicBlock
|
||||
)
|
||||
}
|
||||
|
||||
/** Gets the enclosing scope (the scope whose entry block this def is in). */
|
||||
override Py::Scope getScope() {
|
||||
exists(CfgImpl::BasicBlock bb |
|
||||
this.definesAt(_, bb, -1) and
|
||||
result = bb.getNode(0).(Cfg::ControlFlowNode).getScope()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/** A phi node (alias matching legacy naming). */
|
||||
class PhiFunction extends PhiNode {
|
||||
/**
|
||||
* Gets an input to this phi function (a definition that flows into
|
||||
* the phi from one of its predecessor blocks). Mirrors legacy
|
||||
* ESSA's `PhiFunction.getAnInput()`.
|
||||
*/
|
||||
Ssa::Definition getAnInput() { Ssa::phiHasInputFromBlock(this, result, _) }
|
||||
}
|
||||
|
||||
/** Base class for all ESSA definitions (legacy-shaped). */
|
||||
class EssaDefinition = Ssa::Definition;
|
||||
|
||||
/**
|
||||
* An adapter representing a single SSA-defined "variable" — wrapping
|
||||
* one `Ssa::Definition`. Mirrors legacy `EssaVariable` API.
|
||||
*/
|
||||
class EssaVariable extends Ssa::Definition {
|
||||
/** Gets the underlying SSA definition (legacy name). */
|
||||
Ssa::Definition getDefinition() { result = this }
|
||||
|
||||
/**
|
||||
* Gets a CFG node where this definition is used. Includes regular
|
||||
* `Name` reads as well as the synthetic scope-exit "use" registered
|
||||
* via `SsaImplInput::variableRead` — mirrors legacy ESSA's
|
||||
* `EssaVariable.getAUse()` which inherited the synthetic exit-use
|
||||
* from `SsaSourceVariable`.
|
||||
*/
|
||||
Cfg::ControlFlowNode getAUse() {
|
||||
exists(CfgImpl::BasicBlock bb, int i |
|
||||
Ssa::ssaDefReachesRead(this.getSourceVariable(), this, bb, i) and
|
||||
bb.getNode(i) = result
|
||||
)
|
||||
}
|
||||
|
||||
/** Gets the (textual) name of the underlying variable. */
|
||||
string getName() { result = this.getSourceVariable().getVariable().getId() }
|
||||
|
||||
/** Gets the scope in which this variable lives. */
|
||||
Py::Scope getScope() { result = this.getSourceVariable().getVariable().getScope() }
|
||||
|
||||
/** Gets an ultimate non-phi ancestor of this definition. */
|
||||
EssaVariable getAnUltimateDefinition() {
|
||||
if this instanceof PhiNode
|
||||
then
|
||||
exists(Ssa::Definition input |
|
||||
Ssa::phiHasInputFromBlock(this, input, _) and
|
||||
result = input.(EssaVariable).getAnUltimateDefinition()
|
||||
)
|
||||
else result = this
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adjacent use-use and def-use relations exposed by the shared SSA
|
||||
* library. Provides the same interface as legacy
|
||||
* `semmle.python.essa.SsaCompute::AdjacentUses`.
|
||||
*/
|
||||
module AdjacentUses {
|
||||
/** Holds if `nodeFrom` and `nodeTo` are adjacent uses of the same SSA variable. */
|
||||
predicate adjacentUseUse(Cfg::NameNode nodeFrom, Cfg::NameNode nodeTo) {
|
||||
exists(SsaSourceVariable v, CfgImpl::BasicBlock bb1, int i1, CfgImpl::BasicBlock bb2, int i2 |
|
||||
Ssa::adjacentUseUse(bb1, i1, bb2, i2, v, _) and
|
||||
nodeFrom = bb1.getNode(i1) and
|
||||
nodeTo = bb2.getNode(i2)
|
||||
)
|
||||
}
|
||||
|
||||
/** Holds if `use` is a first use of definition `def`. */
|
||||
predicate firstUse(Ssa::Definition def, Cfg::NameNode use) {
|
||||
exists(CfgImpl::BasicBlock bb, int i |
|
||||
Ssa::firstUse(def, bb, i, _) and
|
||||
use = bb.getNode(i)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `use` is any reachable use of definition `def`. Combines
|
||||
* `firstUse` with transitive use-use adjacency.
|
||||
*/
|
||||
predicate useOfDef(Ssa::Definition def, Cfg::NameNode use) {
|
||||
firstUse(def, use)
|
||||
or
|
||||
exists(Cfg::NameNode mid | useOfDef(def, mid) and adjacentUseUse(mid, use))
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,4 @@
|
||||
private import python
|
||||
private import semmle.python.controlflow.internal.Cfg as Cfg
|
||||
private import semmle.python.dataflow.new.internal.SsaImpl as SsaImpl
|
||||
private import semmle.python.dataflow.new.DataFlow
|
||||
private import semmle.python.dataflow.new.internal.DataFlowPrivate as DataFlowPrivate
|
||||
private import FlowSummaryImpl as FlowSummaryImpl
|
||||
@@ -13,12 +11,34 @@ private import semmle.python.ApiGraphs
|
||||
*/
|
||||
predicate defaultTaintSanitizer(DataFlow::Node node) { none() }
|
||||
|
||||
/**
|
||||
* Holds if default taint tracking should read content `contentSet` implicitly and
|
||||
* propagate taint from a container to reads of that content.
|
||||
*/
|
||||
private predicate defaultTaintReadContent(DataFlow::ContentSet contentSet) {
|
||||
// Tuple and dictionary content is precise, so use wildcard content sets to avoid
|
||||
// blowing up the size of `Stage1::readSetEx` (otherwise this predicate would
|
||||
// expand to one row per (node, distinct key or index) and the framework's
|
||||
// read-set relation grows quadratically). `ContentSet.getAReadContent` expands
|
||||
// these wildcards back to the specific contents when matching against stores.
|
||||
contentSet.isAnyTupleOrDictionaryElement()
|
||||
or
|
||||
// List and set element content is already imprecise, so no wildcard expansion is
|
||||
// needed.
|
||||
contentSet.getAStoreContent() instanceof DataFlow::ListElementContent
|
||||
or
|
||||
contentSet.getAStoreContent() instanceof DataFlow::SetElementContent
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if default `TaintTracking::Configuration`s should allow implicit reads
|
||||
* of `c` at sinks and inputs to additional taint steps.
|
||||
*/
|
||||
bindingset[node]
|
||||
predicate defaultImplicitTaintRead(DataFlow::Node node, DataFlow::ContentSet c) { none() }
|
||||
predicate defaultImplicitTaintRead(DataFlow::Node node, DataFlow::ContentSet c) {
|
||||
exists(node) and
|
||||
defaultTaintReadContent(c)
|
||||
}
|
||||
|
||||
private module Cached {
|
||||
/**
|
||||
@@ -77,7 +97,7 @@ import Cached
|
||||
* and isn't a big problem in practice.
|
||||
*/
|
||||
predicate concatStep(DataFlow::CfgNode nodeFrom, DataFlow::CfgNode nodeTo) {
|
||||
exists(Cfg::BinaryExprNode add | add = nodeTo.getNode() |
|
||||
exists(BinaryExprNode add | add = nodeTo.getNode() |
|
||||
add.getOp() instanceof Add and add.getAnOperand() = nodeFrom.getNode()
|
||||
)
|
||||
}
|
||||
@@ -86,7 +106,7 @@ predicate concatStep(DataFlow::CfgNode nodeFrom, DataFlow::CfgNode nodeTo) {
|
||||
* Holds if taint can flow from `nodeFrom` to `nodeTo` with a step related to subscripting.
|
||||
*/
|
||||
predicate subscriptStep(DataFlow::CfgNode nodeFrom, DataFlow::CfgNode nodeTo) {
|
||||
nodeTo.getNode().(Cfg::SubscriptNode).getObject() = nodeFrom.getNode()
|
||||
nodeTo.getNode().(SubscriptNode).getObject() = nodeFrom.getNode()
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -102,15 +122,15 @@ predicate stringManipulation(DataFlow::CfgNode nodeFrom, DataFlow::CfgNode nodeT
|
||||
(
|
||||
call = API::builtin(["str", "bytes", "unicode"]).getACall()
|
||||
or
|
||||
call.getFunction().asCfgNode().(Cfg::NameNode).getId() in ["str", "bytes", "unicode"]
|
||||
call.getFunction().asCfgNode().(NameNode).getId() in ["str", "bytes", "unicode"]
|
||||
) and
|
||||
nodeFrom in [call.getArg(0), call.getArgByName("object")]
|
||||
)
|
||||
or
|
||||
// String methods. Note that this doesn't recognize `meth = "foo".upper; meth()`
|
||||
exists(Cfg::CallNode call, string method_name, Cfg::ControlFlowNode object |
|
||||
exists(CallNode call, string method_name, ControlFlowNode object |
|
||||
call = nodeTo.getNode() and
|
||||
object = call.getFunction().(Cfg::AttrNode).getObject(method_name)
|
||||
object = call.getFunction().(AttrNode).getObject(method_name)
|
||||
|
|
||||
nodeFrom.getNode() = object and
|
||||
method_name in [
|
||||
@@ -130,18 +150,13 @@ predicate stringManipulation(DataFlow::CfgNode nodeFrom, DataFlow::CfgNode nodeT
|
||||
nodeFrom.getNode() = object and
|
||||
method_name in ["partition", "rpartition", "rsplit", "split", "splitlines"]
|
||||
or
|
||||
// Iterable[str] -> str
|
||||
// TODO: check if these should be handled differently in regards to content
|
||||
method_name = "join" and
|
||||
nodeFrom.getNode() = call.getArg(0)
|
||||
or
|
||||
// Mapping[str, Any] -> str
|
||||
method_name = "format_map" and
|
||||
nodeFrom.getNode() = call.getArg(0)
|
||||
)
|
||||
or
|
||||
// % formatting
|
||||
exists(Cfg::BinaryExprNode fmt | fmt = nodeTo.getNode() |
|
||||
exists(BinaryExprNode fmt | fmt = nodeTo.getNode() |
|
||||
fmt.getOp() instanceof Mod and
|
||||
(
|
||||
fmt.getLeft() = nodeFrom.getNode()
|
||||
@@ -151,7 +166,7 @@ predicate stringManipulation(DataFlow::CfgNode nodeFrom, DataFlow::CfgNode nodeT
|
||||
)
|
||||
or
|
||||
// string multiplication -- `"foo" * 10`
|
||||
exists(Cfg::BinaryExprNode mult | mult = nodeTo.getNode() |
|
||||
exists(BinaryExprNode mult | mult = nodeTo.getNode() |
|
||||
mult.getOp() instanceof Mult and
|
||||
mult.getLeft() = nodeFrom.getNode()
|
||||
)
|
||||
@@ -163,32 +178,21 @@ predicate stringManipulation(DataFlow::CfgNode nodeFrom, DataFlow::CfgNode nodeT
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if taint can flow from `nodeFrom` to `nodeTo` with a step related to containers
|
||||
* (lists/sets/dictionaries): literals, constructor invocation, methods. Note that this
|
||||
* is currently very imprecise, as an example, since we model `dict.get`, we treat any
|
||||
* `<tainted object>.get(<arg>)` will be tainted, whether it's true or not.
|
||||
* Holds if taint can flow from `nodeFrom` to `nodeTo` with a step related to reading
|
||||
* content from containers (lists/sets/dictionaries/tuples): subscripts, iteration,
|
||||
* constructor invocation, methods.
|
||||
*/
|
||||
predicate containerStep(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) {
|
||||
// construction by literal
|
||||
//
|
||||
// TODO: once we have proper flow-summary modeling, we might not need this step any
|
||||
// longer -- but there needs to be a matching read-step for the store-step, and we
|
||||
// don't provide that right now.
|
||||
DataFlowPrivate::listStoreStep(nodeFrom, _, nodeTo)
|
||||
or
|
||||
DataFlowPrivate::setStoreStep(nodeFrom, _, nodeTo)
|
||||
or
|
||||
DataFlowPrivate::tupleStoreStep(nodeFrom, _, nodeTo)
|
||||
or
|
||||
DataFlowPrivate::dictStoreStep(nodeFrom, _, nodeTo)
|
||||
or
|
||||
// comprehension, so there is taint-flow from `x` in `[x for x in xs]` to the
|
||||
// resulting list of the list-comprehension.
|
||||
//
|
||||
// TODO: once we have proper flow-summary modeling, we might not need this step any
|
||||
// longer -- but there needs to be a matching read-step for the store-step, and we
|
||||
// don't provide that right now.
|
||||
DataFlowPrivate::yieldStoreStep(nodeFrom, _, nodeTo)
|
||||
exists(DataFlow::ContentSet contentSet |
|
||||
DataFlowPrivate::readStep(nodeFrom, contentSet, nodeTo) and
|
||||
exists(DataFlow::Content c | c = contentSet.getAReadContent() |
|
||||
c instanceof DataFlow::TupleElementContent or
|
||||
c instanceof DataFlow::DictionaryElementContent or
|
||||
c instanceof DataFlow::DictionaryElementAnyContent or
|
||||
c instanceof DataFlow::ListElementContent or
|
||||
c instanceof DataFlow::SetElementContent
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -209,8 +213,8 @@ predicate awaitStep(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) {
|
||||
* the variable `f` is tainted if the result of `open("foo")` is tainted.
|
||||
*/
|
||||
predicate asyncWithStep(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) {
|
||||
exists(With with, Cfg::ControlFlowNode contextManager, Cfg::ControlFlowNode var |
|
||||
var = any(SsaImpl::WithDefinition wd).getDefiningNode()
|
||||
exists(With with, ControlFlowNode contextManager, ControlFlowNode var |
|
||||
var = any(WithDefinition wd).getDefiningNode()
|
||||
|
|
||||
nodeFrom.(DataFlow::CfgNode).getNode() = contextManager and
|
||||
nodeTo.(DataFlow::CfgNode).getNode() = var and
|
||||
|
||||
@@ -2,8 +2,6 @@ import codeql.util.Unit
|
||||
import codeql.typetracking.TypeTracking as Shared
|
||||
import codeql.typetracking.internal.TypeTrackingImpl as SharedImpl
|
||||
private import python
|
||||
private import semmle.python.controlflow.internal.Cfg as Cfg
|
||||
private import semmle.python.dataflow.new.internal.SsaImpl as SsaImpl
|
||||
private import semmle.python.internal.CachedStages
|
||||
private import semmle.python.dataflow.new.internal.DataFlowPublic as DataFlowPublic
|
||||
private import semmle.python.dataflow.new.internal.DataFlowPrivate as DataFlowPrivate
|
||||
@@ -96,10 +94,8 @@ private module SummaryTypeTrackerInput implements SummaryTypeTracker::Input {
|
||||
Node returnOf(Node callable, SummaryComponent return) {
|
||||
return = FlowSummaryImpl::Private::SummaryComponent::return() and
|
||||
// `result` should be the return value of a callable expression (lambda or function) referenced by `callable`
|
||||
exists(Return ret |
|
||||
ret.getScope() = callable.getALocalSource().asExpr().(CallableExpr).getInnerScope() and
|
||||
result.asCfgNode().getNode() = ret.getValue()
|
||||
)
|
||||
result.asCfgNode() =
|
||||
callable.getALocalSource().asExpr().(CallableExpr).getInnerScope().getAReturnValueFlowNode()
|
||||
}
|
||||
|
||||
// Relating callables to nodes
|
||||
@@ -164,7 +160,7 @@ module TypeTrackingInput implements Shared::TypeTrackingInput<Location> {
|
||||
// ignore the flow steps from the synthetic sequence node to the real sequence node,
|
||||
// since we only support one level of content in type-trackers, and the nested
|
||||
// structure requires two levels at least to be useful.
|
||||
not exists(Cfg::SequenceNode outer |
|
||||
not exists(SequenceNode outer |
|
||||
outer.getAnElement() = nodeTo.asCfgNode() and
|
||||
IterableUnpacking::iterableUnpackingTupleFlowStep(nodeFrom, nodeTo)
|
||||
)
|
||||
@@ -245,7 +241,7 @@ module TypeTrackingInput implements Shared::TypeTrackingInput<Location> {
|
||||
// is only fed set/list content)
|
||||
not nodeFrom instanceof DataFlowPublic::IterableElementNode
|
||||
or
|
||||
TypeTrackerSummaryFlow::basicStoreStep(nodeFrom, nodeTo, content)
|
||||
TypeTrackerSummaryFlow::basicStoreStep(nodeFrom, nodeTo, DataFlowPublic::singleton(content))
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -263,7 +259,7 @@ module TypeTrackingInput implements Shared::TypeTrackingInput<Location> {
|
||||
// Since we only support one level of content in type-trackers we don't actually
|
||||
// support `(aa, ab), (ba, bb) = ...`. Therefore we exclude the read-step from `(aa,
|
||||
// ab)` to `aa` (since it is not needed).
|
||||
not exists(Cfg::SequenceNode outer |
|
||||
not exists(SequenceNode outer |
|
||||
outer.getAnElement() = nodeFrom.asCfgNode() and
|
||||
IterableUnpacking::iterableUnpackingTupleFlowStep(_, nodeFrom)
|
||||
) and
|
||||
@@ -273,17 +269,18 @@ module TypeTrackingInput implements Shared::TypeTrackingInput<Location> {
|
||||
IterableUnpacking::iterableUnpackingForReadStep(_, _, seq) and
|
||||
IterableUnpacking::iterableUnpackingConvertingReadStep(seq, _, elem) and
|
||||
IterableUnpacking::iterableUnpackingConvertingStoreStep(elem, _, nodeFrom) and
|
||||
nodeFrom.asCfgNode() instanceof Cfg::SequenceNode
|
||||
nodeFrom.asCfgNode() instanceof SequenceNode
|
||||
)
|
||||
or
|
||||
TypeTrackerSummaryFlow::basicLoadStep(nodeFrom, nodeTo, content)
|
||||
TypeTrackerSummaryFlow::basicLoadStep(nodeFrom, nodeTo, DataFlowPublic::singleton(content))
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if the `loadContent` of `nodeFrom` is stored in the `storeContent` of `nodeTo`.
|
||||
*/
|
||||
predicate loadStoreStep(Node nodeFrom, Node nodeTo, Content loadContent, Content storeContent) {
|
||||
TypeTrackerSummaryFlow::basicLoadStoreStep(nodeFrom, nodeTo, loadContent, storeContent)
|
||||
TypeTrackerSummaryFlow::basicLoadStoreStep(nodeFrom, nodeTo,
|
||||
DataFlowPublic::singleton(loadContent), DataFlowPublic::singleton(storeContent))
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -310,15 +307,13 @@ module TypeTrackingInput implements Shared::TypeTrackingInput<Location> {
|
||||
//
|
||||
// nodeFrom is `expr`
|
||||
// nodeTo is entry node for `f`
|
||||
exists(
|
||||
SsaImpl::ScopeEntryDefinition e, SsaImpl::SsaSourceVariable var, Cfg::DefinitionNode def
|
||||
|
|
||||
exists(ScopeEntryDefinition e, SsaSourceVariable var, DefinitionNode def |
|
||||
e.getSourceVariable() = var and
|
||||
def.getNode() = var.getVariable().getAStore()
|
||||
var.hasDefiningNode(def)
|
||||
|
|
||||
nodeTo.(DataFlowPublic::ScopeEntryDefinitionNode).getDefinition() = e and
|
||||
nodeFrom.asCfgNode() = def and
|
||||
var.getVariable().getScope().getScope*() = nodeFrom.getScope()
|
||||
var.getScope().getScope*() = nodeFrom.getScope()
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -3,9 +3,6 @@ overlay[local]
|
||||
module;
|
||||
|
||||
private import python
|
||||
private import semmle.python.controlflow.internal.Cfg as Cfg
|
||||
private import semmle.python.controlflow.internal.AstNodeImpl as CfgImpl
|
||||
private import semmle.python.dataflow.new.internal.SsaImpl as SsaImpl
|
||||
private import DataFlowPublic
|
||||
private import semmle.python.dataflow.new.internal.DataFlowPrivate
|
||||
private import codeql.dataflow.VariableCapture as Shared
|
||||
@@ -17,10 +14,10 @@ private import codeql.dataflow.VariableCapture as Shared
|
||||
// The first is the main implementation, the second is a performance motivated restriction.
|
||||
// The restriction is to clear any `CapturedVariableContent` before writing a new one
|
||||
// to avoid long access paths (see the link for a nice explanation).
|
||||
private module CaptureInput implements Shared::InputSig<Location, CfgImpl::BasicBlock> {
|
||||
private module CaptureInput implements Shared::InputSig<Location, Cfg::BasicBlock> {
|
||||
private import python as PY
|
||||
|
||||
additional class ExprCfgNode extends Cfg::ControlFlowNode {
|
||||
additional class ExprCfgNode extends ControlFlowNode {
|
||||
ExprCfgNode() { isExpressionNode(this) }
|
||||
}
|
||||
|
||||
@@ -28,9 +25,7 @@ private module CaptureInput implements Shared::InputSig<Location, CfgImpl::Basic
|
||||
predicate isConstructor() { none() }
|
||||
}
|
||||
|
||||
Callable basicBlockGetEnclosingCallable(CfgImpl::BasicBlock bb) {
|
||||
result = bb.getEnclosingCallable().asScope()
|
||||
}
|
||||
Callable basicBlockGetEnclosingCallable(Cfg::BasicBlock bb) { result = bb.getScope() }
|
||||
|
||||
class CapturedVariable extends LocalVariable {
|
||||
Function f;
|
||||
@@ -56,29 +51,27 @@ private module CaptureInput implements Shared::InputSig<Location, CfgImpl::Basic
|
||||
class CapturedParameter extends CapturedVariable {
|
||||
CapturedParameter() { this.isParameter() }
|
||||
|
||||
Cfg::ControlFlowNode getCfgNode() { result.getNode().(Parameter) = this.getAnAccess() }
|
||||
ControlFlowNode getCfgNode() { result.getNode().(Parameter) = this.getAnAccess() }
|
||||
}
|
||||
|
||||
class Expr extends ExprCfgNode {
|
||||
predicate hasCfgNode(CfgImpl::BasicBlock bb, int i) { this = bb.getNode(i) }
|
||||
predicate hasCfgNode(Cfg::BasicBlock bb, int i) { this = bb.getNode(i) }
|
||||
}
|
||||
|
||||
class VariableWrite extends Cfg::ControlFlowNode {
|
||||
class VariableWrite extends ControlFlowNode {
|
||||
CapturedVariable v;
|
||||
|
||||
VariableWrite() {
|
||||
exists(Cfg::DefinitionNode d | d.getNode() = v.getAStore() | this = d.getValue())
|
||||
}
|
||||
VariableWrite() { this = v.getAStore().getAFlowNode().(DefinitionNode).getValue() }
|
||||
|
||||
CapturedVariable getVariable() { result = v }
|
||||
|
||||
predicate hasCfgNode(CfgImpl::BasicBlock bb, int i) { this = bb.getNode(i) }
|
||||
predicate hasCfgNode(Cfg::BasicBlock bb, int i) { this = bb.getNode(i) }
|
||||
}
|
||||
|
||||
class VariableRead extends Expr {
|
||||
CapturedVariable v;
|
||||
|
||||
VariableRead() { this.getNode() = v.getALoad() }
|
||||
VariableRead() { this = v.getALoad().getAFlowNode() }
|
||||
|
||||
CapturedVariable getVariable() { result = v }
|
||||
}
|
||||
@@ -87,14 +80,9 @@ private module CaptureInput implements Shared::InputSig<Location, CfgImpl::Basic
|
||||
// TODO: Other languages have an extra case here looking like
|
||||
// simpleAstFlowStep(nodeFrom, nodeTo)
|
||||
// we should investigate the potential benefit of adding that.
|
||||
exists(SsaImpl::EssaVariable def |
|
||||
exists(SsaVariable def |
|
||||
def.getAUse() = nodeTo and
|
||||
def.getAnUltimateDefinition()
|
||||
.getDefinition()
|
||||
.(SsaImpl::EssaNodeDefinition)
|
||||
.getDefiningNode()
|
||||
.(Cfg::DefinitionNode)
|
||||
.getValue() = nodeFrom
|
||||
def.getAnUltimateDefinition().getDefinition().(DefinitionNode).getValue() = nodeFrom
|
||||
)
|
||||
}
|
||||
|
||||
@@ -119,7 +107,7 @@ class CapturedVariable = CaptureInput::CapturedVariable;
|
||||
|
||||
class ClosureExpr = CaptureInput::ClosureExpr;
|
||||
|
||||
module Flow = Shared::Flow<Location, Cfg::CfgSigImpl, CaptureInput>;
|
||||
module Flow = Shared::Flow<Location, Cfg, CaptureInput>;
|
||||
|
||||
private Flow::ClosureNode asClosureNode(Node n) {
|
||||
result = n.(SynthCaptureNode).getSynthesizedCaptureNode()
|
||||
|
||||
@@ -448,7 +448,8 @@ class TaintTrackingImplementation extends string instanceof TaintTracking::Confi
|
||||
context = TNoParam() and
|
||||
src = TTaintTrackingNode_(retval, TNoParam(), path, kind, this) and
|
||||
node.asCfgNode() = call and
|
||||
retval.asCfgNode().getNode() = any(Return ret | ret.getScope() = pyfunc.getScope()).getValue()
|
||||
retval.asCfgNode() =
|
||||
any(Return ret | ret.getScope() = pyfunc.getScope()).getValue().getAFlowNode()
|
||||
) and
|
||||
edgeLabel = "return"
|
||||
}
|
||||
@@ -470,7 +471,8 @@ class TaintTrackingImplementation extends string instanceof TaintTracking::Confi
|
||||
this.callContexts(call, src, pyfunc, context, callee) and
|
||||
retnode = TTaintTrackingNode_(retval, callee, path, kind, this) and
|
||||
node.asCfgNode() = call and
|
||||
retval.asCfgNode().getNode() = any(Return ret | ret.getScope() = pyfunc.getScope()).getValue()
|
||||
retval.asCfgNode() =
|
||||
any(Return ret | ret.getScope() = pyfunc.getScope()).getValue().getAFlowNode()
|
||||
) and
|
||||
edgeLabel = "call"
|
||||
}
|
||||
@@ -714,10 +716,8 @@ private class EssaTaintTracking extends string instanceof TaintTracking::Configu
|
||||
src = TTaintTrackingNode_(srcnode, context, path, srckind, this) and
|
||||
path.noAttribute()
|
||||
|
|
||||
srcnode.asCfgNode().getNode() = assign.getValue() and
|
||||
exists(SequenceNode left_parent | left_parent.getNode() = assign.getATarget() |
|
||||
depth = iterable_unpacking_descent(left_parent, defn.getDefiningNode())
|
||||
) and
|
||||
assign.getValue().getAFlowNode() = srcnode.asCfgNode() and
|
||||
depth = iterable_unpacking_descent(assign.getATarget().getAFlowNode(), defn.getDefiningNode()) and
|
||||
kind = taint_at_depth(srckind, depth)
|
||||
)
|
||||
}
|
||||
@@ -964,7 +964,7 @@ private TaintKind taint_at_depth(SequenceKind parent_kind, int depth) {
|
||||
* - with `left_defn` = `*y`, `left_parent` = `((x, *y), ...)`, result = 1
|
||||
*/
|
||||
int iterable_unpacking_descent(SequenceNode left_parent, ControlFlowNode left_defn) {
|
||||
exists(Assign a | left_parent.getNode() = a.getATarget().getASubExpression*()) and
|
||||
exists(Assign a | a.getATarget().getASubExpression*().getAFlowNode() = left_parent) and
|
||||
left_parent.getAnElement() = left_defn and
|
||||
// Handle `a, *b = some_iterable`
|
||||
if left_defn instanceof StarredNode then result = 0 else result = 1
|
||||
|
||||
@@ -56,7 +56,7 @@ module SsaSource {
|
||||
predicate with_definition(Variable v, ControlFlowNode defn) {
|
||||
exists(With with, Name var |
|
||||
with.getOptionalVars() = var and
|
||||
defn.getNode() = var
|
||||
var.getAFlowNode() = defn
|
||||
|
|
||||
var = v.getAStore()
|
||||
)
|
||||
@@ -67,7 +67,7 @@ module SsaSource {
|
||||
predicate pattern_capture_definition(Variable v, ControlFlowNode defn) {
|
||||
exists(MatchCapturePattern capture, Name var |
|
||||
capture.getVariable() = var and
|
||||
defn.getNode() = var
|
||||
var.getAFlowNode() = defn
|
||||
|
|
||||
var = v.getAStore()
|
||||
)
|
||||
@@ -78,7 +78,7 @@ module SsaSource {
|
||||
predicate pattern_alias_definition(Variable v, ControlFlowNode defn) {
|
||||
exists(MatchAsPattern pattern, Name var |
|
||||
pattern.getAlias() = var and
|
||||
defn.getNode() = var
|
||||
var.getAFlowNode() = defn
|
||||
|
|
||||
var = v.getAStore()
|
||||
)
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
*/
|
||||
|
||||
private import python
|
||||
private import semmle.python.controlflow.internal.Cfg as Cfg
|
||||
private import semmle.python.Concepts
|
||||
private import semmle.python.ApiGraphs
|
||||
private import semmle.python.dataflow.new.RemoteFlowSources
|
||||
@@ -60,7 +59,7 @@ module Bottle {
|
||||
|
||||
override Parameter getARoutedParameter() { none() }
|
||||
|
||||
override Function getARequestHandler() { node.getNode() = result.getADecorator() }
|
||||
override Function getARequestHandler() { result.getADecorator().getAFlowNode() = node }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -74,10 +73,7 @@ module Bottle {
|
||||
/** A response returned by a view callable. */
|
||||
class BottleReturnResponse extends Http::Server::HttpResponse::Range {
|
||||
BottleReturnResponse() {
|
||||
exists(Return ret |
|
||||
ret.getScope() = any(View::ViewCallable vc) and
|
||||
this.asCfgNode().getNode() = ret.getValue()
|
||||
)
|
||||
this.asCfgNode() = any(View::ViewCallable vc).getAReturnValueFlowNode()
|
||||
}
|
||||
|
||||
override DataFlow::Node getBody() { result = this }
|
||||
@@ -158,9 +154,9 @@ module Bottle {
|
||||
DataFlow::Node value;
|
||||
|
||||
HeaderWriteSubscript() {
|
||||
exists(Cfg::SubscriptNode subscript |
|
||||
exists(SubscriptNode subscript |
|
||||
this.asCfgNode() = subscript and
|
||||
value.asCfgNode() = subscript.(Cfg::DefinitionNode).getValue() and
|
||||
value.asCfgNode() = subscript.(DefinitionNode).getValue() and
|
||||
name.asCfgNode() = subscript.getIndex() and
|
||||
subscript.getObject() = headers().asSource().asCfgNode()
|
||||
)
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
*/
|
||||
|
||||
private import python
|
||||
private import semmle.python.controlflow.internal.Cfg as Cfg
|
||||
private import semmle.python.dataflow.new.DataFlow
|
||||
private import semmle.python.dataflow.new.RemoteFlowSources
|
||||
private import semmle.python.dataflow.new.TaintTracking
|
||||
@@ -1306,7 +1305,7 @@ module PrivateDjango {
|
||||
dict.(DataFlow::MethodCallNode).calls(files, "dict")
|
||||
)
|
||||
|
|
||||
this.asCfgNode().(Cfg::SubscriptNode).getObject() = dict.asCfgNode()
|
||||
this.asCfgNode().(SubscriptNode).getObject() = dict.asCfgNode()
|
||||
or
|
||||
this.(DataFlow::MethodCallNode).calls(dict, "get")
|
||||
)
|
||||
@@ -1315,7 +1314,7 @@ module PrivateDjango {
|
||||
exists(DataFlow::AttrRead files, DataFlow::MethodCallNode getlistCall |
|
||||
files.accesses(instance(), "FILES") and
|
||||
getlistCall.calls(files, "getlist") and
|
||||
this.asCfgNode().(Cfg::SubscriptNode).getObject() = getlistCall.asCfgNode()
|
||||
this.asCfgNode().(SubscriptNode).getObject() = getlistCall.asCfgNode()
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -2217,7 +2216,7 @@ module PrivateDjango {
|
||||
DataFlow::Node value;
|
||||
|
||||
DjangoResponseCookieSubscriptWrite() {
|
||||
exists(Cfg::SubscriptNode subscript, DataFlow::AttrRead cookieLookup |
|
||||
exists(SubscriptNode subscript, DataFlow::AttrRead cookieLookup |
|
||||
// To give `this` a value, we need to choose between either LHS or RHS,
|
||||
// and just go with the LHS
|
||||
this.asCfgNode() = subscript
|
||||
@@ -2229,7 +2228,7 @@ module PrivateDjango {
|
||||
|
|
||||
cookieLookup.flowsTo(subscriptObj)
|
||||
) and
|
||||
value.asCfgNode() = subscript.(Cfg::DefinitionNode).getValue() and
|
||||
value.asCfgNode() = subscript.(DefinitionNode).getValue() and
|
||||
index.asCfgNode() = subscript.getIndex()
|
||||
)
|
||||
}
|
||||
@@ -2250,7 +2249,7 @@ module PrivateDjango {
|
||||
DataFlow::Node value;
|
||||
|
||||
DjangoResponseHeaderSubscriptWrite() {
|
||||
exists(Cfg::SubscriptNode subscript, DataFlow::AttrRead headerLookup |
|
||||
exists(SubscriptNode subscript, DataFlow::AttrRead headerLookup |
|
||||
// To give `this` a value, we need to choose between either LHS or RHS,
|
||||
// and just go with the LHS
|
||||
this.asCfgNode() = subscript
|
||||
@@ -2262,7 +2261,7 @@ module PrivateDjango {
|
||||
|
|
||||
headerLookup.flowsTo(subscriptObj)
|
||||
) and
|
||||
value.asCfgNode() = subscript.(Cfg::DefinitionNode).getValue() and
|
||||
value.asCfgNode() = subscript.(DefinitionNode).getValue() and
|
||||
index.asCfgNode() = subscript.getIndex()
|
||||
)
|
||||
}
|
||||
@@ -2285,14 +2284,14 @@ module PrivateDjango {
|
||||
DataFlow::Node value;
|
||||
|
||||
DjangoResponseSubscriptWrite() {
|
||||
exists(Cfg::SubscriptNode subscript |
|
||||
exists(SubscriptNode subscript |
|
||||
// To give `this` a value, we need to choose between either LHS or RHS,
|
||||
// and just go with the LHS
|
||||
this.asCfgNode() = subscript
|
||||
|
|
||||
subscript.getObject() =
|
||||
DjangoImpl::DjangoHttp::Response::HttpResponse::instance().asCfgNode() and
|
||||
value.asCfgNode() = subscript.(Cfg::DefinitionNode).getValue() and
|
||||
value.asCfgNode() = subscript.(DefinitionNode).getValue() and
|
||||
index.asCfgNode() = subscript.getIndex()
|
||||
)
|
||||
}
|
||||
@@ -2427,7 +2426,7 @@ module PrivateDjango {
|
||||
/** Gets a reference to the result of calling the `as_view` classmethod of this class. */
|
||||
private DataFlow::TypeTrackingNode asViewResult(DataFlow::TypeTracker t) {
|
||||
t.start() and
|
||||
result.asCfgNode().(Cfg::CallNode).getFunction() = this.asViewRef().asCfgNode()
|
||||
result.asCfgNode().(CallNode).getFunction() = this.asViewRef().asCfgNode()
|
||||
or
|
||||
exists(DataFlow::TypeTracker t2 | result = this.asViewResult(t2).track(t2, t))
|
||||
}
|
||||
@@ -2873,10 +2872,7 @@ module PrivateDjango {
|
||||
DataFlow::CfgNode
|
||||
{
|
||||
DjangoRedirectViewGetRedirectUrlReturn() {
|
||||
exists(Return ret |
|
||||
ret.getScope() = any(GetRedirectUrlFunction f) and
|
||||
node.getNode() = ret.getValue()
|
||||
)
|
||||
node = any(GetRedirectUrlFunction f).getAReturnValueFlowNode()
|
||||
}
|
||||
|
||||
override DataFlow::Node getRedirectLocation() { result = this }
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
*/
|
||||
|
||||
private import python
|
||||
private import semmle.python.controlflow.internal.Cfg as Cfg
|
||||
private import semmle.python.dataflow.new.DataFlow
|
||||
private import semmle.python.dataflow.new.RemoteFlowSources
|
||||
private import semmle.python.dataflow.new.TaintTracking
|
||||
@@ -130,7 +129,7 @@ module FastApi {
|
||||
result in [this.getArg(0), this.getArgByName("path")]
|
||||
}
|
||||
|
||||
override Function getARequestHandler() { node.getNode() = result.getADecorator() }
|
||||
override Function getARequestHandler() { result.getADecorator().getAFlowNode() = node }
|
||||
|
||||
override string getFramework() { result = "FastAPI" }
|
||||
|
||||
@@ -310,10 +309,7 @@ module FastApi {
|
||||
FastApiRouteSetup routeSetup;
|
||||
|
||||
FastApiRequestHandlerReturn() {
|
||||
exists(Return ret |
|
||||
ret.getScope() = routeSetup.getARequestHandler() and
|
||||
node.getNode() = ret.getValue()
|
||||
)
|
||||
node = routeSetup.getARequestHandler().getAReturnValueFlowNode()
|
||||
}
|
||||
|
||||
override DataFlow::Node getBody() { result = this }
|
||||
@@ -442,7 +438,7 @@ module FastApi {
|
||||
DataFlow::Node value;
|
||||
|
||||
HeaderSubscriptWrite() {
|
||||
exists(Cfg::SubscriptNode subscript, DataFlow::AttrRead headerLookup |
|
||||
exists(SubscriptNode subscript, DataFlow::AttrRead headerLookup |
|
||||
// To give `this` a value, we need to choose between either LHS or RHS,
|
||||
// and just go with the LHS
|
||||
this.asCfgNode() = subscript
|
||||
@@ -451,7 +447,7 @@ module FastApi {
|
||||
exists(DataFlow::Node subscriptObj | subscriptObj.asCfgNode() = subscript.getObject() |
|
||||
headerLookup.flowsTo(subscriptObj)
|
||||
) and
|
||||
value.asCfgNode() = subscript.(Cfg::DefinitionNode).getValue() and
|
||||
value.asCfgNode() = subscript.(DefinitionNode).getValue() and
|
||||
index.asCfgNode() = subscript.getIndex()
|
||||
)
|
||||
}
|
||||
|
||||
@@ -371,7 +371,7 @@ module Flask {
|
||||
result in [this.getArg(0), this.getArgByName("rule")]
|
||||
}
|
||||
|
||||
override Function getARequestHandler() { node.getNode() = result.getADecorator() }
|
||||
override Function getARequestHandler() { result.getADecorator().getAFlowNode() = node }
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -536,7 +536,7 @@ module Flask {
|
||||
FlaskRouteHandlerReturn() {
|
||||
exists(Function routeHandler |
|
||||
routeHandler = any(FlaskRouteSetup rs).getARequestHandler() and
|
||||
exists(Return ret | ret.getScope() = routeHandler and node.getNode() = ret.getValue()) and
|
||||
node = routeHandler.getAReturnValueFlowNode() and
|
||||
not this instanceof Flask::Response::InstanceSource
|
||||
)
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@ private module FlaskAdmin {
|
||||
result in [this.getArg(0), this.getArgByName("url")]
|
||||
}
|
||||
|
||||
override Function getARequestHandler() { node.getNode() = result.getADecorator() }
|
||||
override Function getARequestHandler() { result.getADecorator().getAFlowNode() = node }
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -71,7 +71,7 @@ private module FlaskAdmin {
|
||||
|
||||
override Function getARequestHandler() {
|
||||
exists(Flask::FlaskViewClass cls |
|
||||
node.getNode() = cls.getADecorator() and
|
||||
cls.getADecorator().getAFlowNode() = node and
|
||||
result = cls.getARequestHandler()
|
||||
)
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
*/
|
||||
|
||||
import python
|
||||
private import semmle.python.controlflow.internal.Cfg as Cfg
|
||||
import semmle.python.dataflow.new.RemoteFlowSources
|
||||
import semmle.python.dataflow.new.TaintTracking
|
||||
import semmle.python.ApiGraphs
|
||||
@@ -52,9 +51,9 @@ module Gradio {
|
||||
// limit only to lists of parameters given to `inputs`.
|
||||
(
|
||||
(
|
||||
call.getKeywordParameter("inputs").asSink().asCfgNode() instanceof Cfg::ListNode
|
||||
call.getKeywordParameter("inputs").asSink().asCfgNode() instanceof ListNode
|
||||
or
|
||||
call.getParameter(1).asSink().asCfgNode() instanceof Cfg::ListNode
|
||||
call.getParameter(1).asSink().asCfgNode() instanceof ListNode
|
||||
) and
|
||||
(
|
||||
this = call.getKeywordParameter("inputs").getASubscript().getAValueReachingSink()
|
||||
@@ -76,8 +75,8 @@ module Gradio {
|
||||
exists(GradioInput call |
|
||||
this = call.getParameter(0, "fn").getParameter(_).asSource() and
|
||||
// exclude lists of parameters given to `inputs`
|
||||
not call.getKeywordParameter("inputs").asSink().asCfgNode() instanceof Cfg::ListNode and
|
||||
not call.getParameter(1).asSink().asCfgNode() instanceof Cfg::ListNode
|
||||
not call.getKeywordParameter("inputs").asSink().asCfgNode() instanceof ListNode and
|
||||
not call.getParameter(1).asSink().asCfgNode() instanceof ListNode
|
||||
)
|
||||
}
|
||||
|
||||
@@ -106,16 +105,16 @@ module Gradio {
|
||||
// handle cases where there are multiple arguments passed as a list to `inputs`
|
||||
(
|
||||
(
|
||||
node.getKeywordParameter("inputs").asSink().asCfgNode() instanceof Cfg::ListNode
|
||||
node.getKeywordParameter("inputs").asSink().asCfgNode() instanceof ListNode
|
||||
or
|
||||
node.getParameter(1).asSink().asCfgNode() instanceof Cfg::ListNode
|
||||
node.getParameter(1).asSink().asCfgNode() instanceof ListNode
|
||||
) and
|
||||
exists(int i | nodeTo = node.getParameter(0, "fn").getParameter(i).asSource() |
|
||||
nodeFrom.asCfgNode() =
|
||||
node.getKeywordParameter("inputs").asSink().asCfgNode().(Cfg::ListNode).getElement(i)
|
||||
node.getKeywordParameter("inputs").asSink().asCfgNode().(ListNode).getElement(i)
|
||||
or
|
||||
nodeFrom.asCfgNode() =
|
||||
node.getParameter(1).asSink().asCfgNode().(Cfg::ListNode).getElement(i)
|
||||
node.getParameter(1).asSink().asCfgNode().(ListNode).getElement(i)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
*/
|
||||
|
||||
private import python
|
||||
private import semmle.python.controlflow.internal.Cfg as Cfg
|
||||
private import semmle.python.dataflow.new.DataFlow
|
||||
private import semmle.python.dataflow.new.TaintTracking
|
||||
private import semmle.python.Concepts
|
||||
@@ -47,7 +46,7 @@ module MarkupSafeModel {
|
||||
|
||||
/** A direct instantiation of `markupsafe.Markup`. */
|
||||
private class ClassInstantiation extends InstanceSource, DataFlow::CallCfgNode {
|
||||
override Cfg::CallNode node;
|
||||
override CallNode node;
|
||||
|
||||
ClassInstantiation() { this = classRef().getACall() }
|
||||
}
|
||||
@@ -65,7 +64,7 @@ module MarkupSafeModel {
|
||||
|
||||
/** A string concatenation with a `markupsafe.Markup` involved. */
|
||||
class StringConcat extends Markup::InstanceSource, DataFlow::CfgNode {
|
||||
override Cfg::BinaryExprNode node;
|
||||
override BinaryExprNode node;
|
||||
|
||||
StringConcat() {
|
||||
node.getOp() instanceof Add and
|
||||
@@ -80,7 +79,7 @@ module MarkupSafeModel {
|
||||
|
||||
/** A %-style string format with `markupsafe.Markup` as the format string. */
|
||||
class PercentStringFormat extends Markup::InstanceSource, DataFlow::CfgNode {
|
||||
override Cfg::BinaryExprNode node;
|
||||
override BinaryExprNode node;
|
||||
|
||||
PercentStringFormat() {
|
||||
node.getOp() instanceof Mod and
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
*/
|
||||
|
||||
private import python
|
||||
private import semmle.python.controlflow.internal.Cfg as Cfg
|
||||
private import semmle.python.Concepts
|
||||
private import semmle.python.ApiGraphs
|
||||
private import semmle.python.frameworks.data.ModelsAsData
|
||||
@@ -57,7 +56,7 @@ module Pycurl {
|
||||
{
|
||||
OutgoingRequestCall() {
|
||||
this = setopt().getACall() and
|
||||
this.getArg(0).asCfgNode().(Cfg::AttrNode).getName() = "URL"
|
||||
this.getArg(0).asCfgNode().(AttrNode).getName() = "URL"
|
||||
}
|
||||
|
||||
override DataFlow::Node getAUrlPart() {
|
||||
@@ -82,7 +81,7 @@ module Pycurl {
|
||||
private class CurlSslCall extends Http::Client::Request::Range instanceof DataFlow::CallCfgNode {
|
||||
CurlSslCall() {
|
||||
this = setopt().getACall() and
|
||||
this.getArg(0).asCfgNode().(Cfg::AttrNode).getName() = ["SSL_VERIFYPEER", "SSL_VERIFYHOST"]
|
||||
this.getArg(0).asCfgNode().(AttrNode).getName() = ["SSL_VERIFYPEER", "SSL_VERIFYHOST"]
|
||||
}
|
||||
|
||||
override DataFlow::Node getAUrlPart() { none() }
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
*/
|
||||
|
||||
private import python
|
||||
private import semmle.python.controlflow.internal.Cfg as Cfg
|
||||
private import semmle.python.dataflow.new.DataFlow
|
||||
private import semmle.python.dataflow.new.TaintTracking
|
||||
private import semmle.python.Concepts
|
||||
@@ -94,7 +93,7 @@ module Pydantic {
|
||||
// be a Pydantic model. So `model[0]` will be an overapproximation, but should not
|
||||
// really cause problems (since we don't expect real code to contain such accesses)
|
||||
nodeFrom = instance() and
|
||||
nodeTo.asCfgNode().(Cfg::SubscriptNode).getObject() = nodeFrom.asCfgNode()
|
||||
nodeTo.asCfgNode().(SubscriptNode).getObject() = nodeFrom.asCfgNode()
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -166,10 +166,7 @@ module Pyramid {
|
||||
/** A response returned by a view callable. */
|
||||
private class PyramidReturnResponse extends Http::Server::HttpResponse::Range {
|
||||
PyramidReturnResponse() {
|
||||
exists(Return ret |
|
||||
ret.getScope() = any(View::ViewCallable vc) and
|
||||
this.asCfgNode().getNode() = ret.getValue()
|
||||
) and
|
||||
this.asCfgNode() = any(View::ViewCallable vc).getAReturnValueFlowNode() and
|
||||
not this = instance()
|
||||
}
|
||||
|
||||
|
||||
@@ -6,7 +6,6 @@ overlay[local?]
|
||||
module;
|
||||
|
||||
private import python
|
||||
private import semmle.python.controlflow.internal.Cfg as Cfg
|
||||
private import semmle.python.dataflow.new.DataFlow
|
||||
private import semmle.python.dataflow.new.TaintTracking
|
||||
private import semmle.python.dataflow.new.RemoteFlowSources
|
||||
@@ -1247,7 +1246,7 @@ module StdlibPrivate {
|
||||
/** An additional taint step for calls to `os.path.join` */
|
||||
private class OsPathJoinCallAdditionalTaintStep extends TaintTracking::AdditionalTaintStep {
|
||||
override predicate step(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) {
|
||||
exists(Cfg::CallNode call |
|
||||
exists(CallNode call |
|
||||
nodeTo.asCfgNode() = call and
|
||||
call = OS::OsPath::join().getACall().asCfgNode() and
|
||||
call.getAnArg() = nodeFrom.asCfgNode()
|
||||
@@ -1318,13 +1317,13 @@ module StdlibPrivate {
|
||||
// run, so if we're able to, we only mark the first element as the command
|
||||
// (and not the arguments to the command).
|
||||
//
|
||||
result.asCfgNode() = arg_args.asCfgNode().(Cfg::SequenceNode).getElement(0)
|
||||
result.asCfgNode() = arg_args.asCfgNode().(SequenceNode).getElement(0)
|
||||
or
|
||||
// Either the "args" argument is not a sequence (which is valid) or we where
|
||||
// just not able to figure it out. Simply mark the "args" argument as the
|
||||
// command.
|
||||
//
|
||||
not arg_args.asCfgNode() instanceof Cfg::SequenceNode and
|
||||
not arg_args.asCfgNode() instanceof SequenceNode and
|
||||
result = arg_args
|
||||
)
|
||||
)
|
||||
@@ -1543,7 +1542,7 @@ module StdlibPrivate {
|
||||
* See https://docs.python.org/3/library/functions.html#eval
|
||||
*/
|
||||
private class BuiltinsEvalCall extends CodeExecution::Range, DataFlow::CallCfgNode {
|
||||
override Cfg::CallNode node;
|
||||
override CallNode node;
|
||||
|
||||
BuiltinsEvalCall() { this = API::builtin("eval").getACall() }
|
||||
|
||||
@@ -1924,7 +1923,7 @@ module StdlibPrivate {
|
||||
nodeFrom = instance().getAValueReachableFromSource() and
|
||||
nodeTo = [getvalueRef(), getfirstRef(), getlistRef()].getAValueReachableFromSource()
|
||||
or
|
||||
nodeFrom.asCfgNode() = nodeTo.asCfgNode().(Cfg::CallNode).getFunction() and
|
||||
nodeFrom.asCfgNode() = nodeTo.asCfgNode().(CallNode).getFunction() and
|
||||
(
|
||||
nodeFrom = getvalueRef().getAValueReachableFromSource() and
|
||||
nodeTo = getvalueResult().asSource()
|
||||
@@ -1940,7 +1939,7 @@ module StdlibPrivate {
|
||||
nodeFrom in [
|
||||
instance().getAValueReachableFromSource(), fieldList().getAValueReachableFromSource()
|
||||
] and
|
||||
nodeTo.asCfgNode().(Cfg::SubscriptNode).getObject() = nodeFrom.asCfgNode()
|
||||
nodeTo.asCfgNode().(SubscriptNode).getObject() = nodeFrom.asCfgNode()
|
||||
or
|
||||
// Attributes on Field
|
||||
nodeFrom = field().getAValueReachableFromSource() and
|
||||
@@ -2255,9 +2254,8 @@ module StdlibPrivate {
|
||||
DataFlow::CfgNode
|
||||
{
|
||||
WsgirefSimpleServerApplicationReturn() {
|
||||
exists(Return ret |
|
||||
ret.getScope() = any(WsgirefSimpleServerApplication requestHandler) and
|
||||
node.getNode() = ret.getValue()
|
||||
exists(WsgirefSimpleServerApplication requestHandler |
|
||||
node = requestHandler.getAReturnValueFlowNode()
|
||||
)
|
||||
}
|
||||
|
||||
@@ -2339,9 +2337,9 @@ module StdlibPrivate {
|
||||
DataFlow::Node value;
|
||||
|
||||
HeaderWriteSubscript() {
|
||||
exists(Cfg::SubscriptNode subscript |
|
||||
exists(SubscriptNode subscript |
|
||||
this.asCfgNode() = subscript and
|
||||
value.asCfgNode() = subscript.(Cfg::DefinitionNode).getValue() and
|
||||
value.asCfgNode() = subscript.(DefinitionNode).getValue() and
|
||||
name.asCfgNode() = subscript.getIndex() and
|
||||
subscript.getObject() = instance().asCfgNode()
|
||||
)
|
||||
@@ -2683,7 +2681,7 @@ module StdlibPrivate {
|
||||
or
|
||||
// Data injection
|
||||
// Special handling of the `/` operator
|
||||
exists(Cfg::BinaryExprNode slash, DataFlow::Node pathOperand, DataFlow::TypeTracker t2 |
|
||||
exists(BinaryExprNode slash, DataFlow::Node pathOperand, DataFlow::TypeTracker t2 |
|
||||
slash.getOp() instanceof Div and
|
||||
pathOperand.asCfgNode() = slash.getAnOperand() and
|
||||
pathlibPath(t2).flowsTo(pathOperand) and
|
||||
@@ -2808,7 +2806,7 @@ module StdlibPrivate {
|
||||
pathlibPath().flowsTo(nodeTo) and
|
||||
(
|
||||
// Special handling of the `/` operator
|
||||
exists(Cfg::BinaryExprNode slash, DataFlow::Node pathOperand |
|
||||
exists(BinaryExprNode slash, DataFlow::Node pathOperand |
|
||||
slash.getOp() instanceof Div and
|
||||
pathOperand.asCfgNode() = slash.getAnOperand() and
|
||||
pathlibPath().flowsTo(pathOperand)
|
||||
@@ -4246,6 +4244,7 @@ module StdlibPrivate {
|
||||
)
|
||||
// TODO: Once we have DictKeyContent, we need to transform that into ListElementContent
|
||||
) and
|
||||
// Element content is mutated into list element content
|
||||
output = "ReturnValue.ListElement" and
|
||||
preservesValue = true
|
||||
or
|
||||
@@ -4272,11 +4271,9 @@ module StdlibPrivate {
|
||||
preservesValue = true
|
||||
)
|
||||
or
|
||||
// TODO: We need to also translate iterable content such as list element
|
||||
// but we currently lack TupleElementAny
|
||||
input = "Argument[0]" and
|
||||
input = "Argument[0].ListElement" and
|
||||
output = "ReturnValue" and
|
||||
preservesValue = false
|
||||
preservesValue = true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4607,9 +4604,9 @@ module StdlibPrivate {
|
||||
}
|
||||
|
||||
override predicate propagatesFlow(string input, string output, boolean preservesValue) {
|
||||
exists(Cfg::CallNode c, string name, Cfg::ControlFlowNode n, DataFlow::AttributeContent ac |
|
||||
c.getFunction().(Cfg::NameNode).getId() = "replace" or
|
||||
c.getFunction().(Cfg::AttrNode).getName() = "replace"
|
||||
exists(CallNode c, string name, ControlFlowNode n, DataFlow::AttributeContent ac |
|
||||
c.getFunction().(NameNode).getId() = "replace" or
|
||||
c.getFunction().(AttrNode).getName() = "replace"
|
||||
|
|
||||
n = c.getArgByName(name) and
|
||||
ac.getAttribute() = name and
|
||||
@@ -4971,6 +4968,26 @@ module StdlibPrivate {
|
||||
}
|
||||
}
|
||||
|
||||
/** A flow summary for `str.join`. */
|
||||
class StrJoinSummary extends SummarizedCallable::Range {
|
||||
StrJoinSummary() { this = "str.join" }
|
||||
|
||||
override DataFlow::CallCfgNode getACall() { result.(DataFlow::MethodCallNode).calls(_, "join") }
|
||||
|
||||
override DataFlow::ArgumentNode getACallback() {
|
||||
result.(DataFlow::AttrRead).getAttributeName() = "join"
|
||||
}
|
||||
|
||||
override predicate propagatesFlow(string input, string output, boolean preservesValue) {
|
||||
(
|
||||
// For code like `" ".join([name])`
|
||||
input = "Argument[0,iterable:].ListElement" and
|
||||
preservesValue = true
|
||||
) and
|
||||
output = "ReturnValue"
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// asyncio
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -5153,10 +5170,10 @@ module StdlibPrivate {
|
||||
* See https://docs.python.org/3.9/library/stdtypes.html#str.startswith
|
||||
*/
|
||||
private class StartswithCall extends Path::SafeAccessCheck::Range {
|
||||
StartswithCall() { this.(Cfg::CallNode).getFunction().(Cfg::AttrNode).getName() = "startswith" }
|
||||
StartswithCall() { this.(CallNode).getFunction().(AttrNode).getName() = "startswith" }
|
||||
|
||||
override predicate checks(Cfg::ControlFlowNode node, boolean branch) {
|
||||
node = this.(Cfg::CallNode).getFunction().(Cfg::AttrNode).getObject() and
|
||||
override predicate checks(ControlFlowNode node, boolean branch) {
|
||||
node = this.(CallNode).getFunction().(AttrNode).getObject() and
|
||||
branch = true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
*/
|
||||
|
||||
private import python
|
||||
private import semmle.python.controlflow.internal.Cfg as Cfg
|
||||
private import semmle.python.Concepts
|
||||
private import semmle.python.ApiGraphs
|
||||
private import semmle.python.security.dataflow.UrlRedirectCustomizations
|
||||
@@ -92,7 +91,7 @@ private module Urllib {
|
||||
* A read of the `netloc` attribute of a parsed URL as returned by `urllib.parse.urlparse`,
|
||||
* which is being checked in a way that is relevant for URL redirection vulnerabilities.
|
||||
*/
|
||||
private predicate netlocCheck(DataFlow::GuardNode g, Cfg::ControlFlowNode node, boolean branch) {
|
||||
private predicate netlocCheck(DataFlow::GuardNode g, ControlFlowNode node, boolean branch) {
|
||||
exists(DataFlow::CallCfgNode urlParseCall, DataFlow::AttrRead netlocRead |
|
||||
urlParseCall = getUrlParseCall() and
|
||||
netlocRead = urlParseCall.getAnAttributeRead("netloc") and
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
*/
|
||||
|
||||
private import python
|
||||
private import semmle.python.controlflow.internal.Cfg as Cfg
|
||||
private import semmle.python.dataflow.new.DataFlow
|
||||
private import semmle.python.dataflow.new.RemoteFlowSources
|
||||
private import semmle.python.dataflow.new.TaintTracking
|
||||
@@ -73,9 +72,9 @@ module Tornado {
|
||||
DataFlow::Node value;
|
||||
|
||||
TornadoHeaderSubscriptWrite() {
|
||||
exists(Cfg::SubscriptNode subscript |
|
||||
exists(SubscriptNode subscript |
|
||||
subscript.getObject() = instance().asCfgNode() and
|
||||
value.asCfgNode() = subscript.(Cfg::DefinitionNode).getValue() and
|
||||
value.asCfgNode() = subscript.(DefinitionNode).getValue() and
|
||||
index.asCfgNode() = subscript.getIndex() and
|
||||
this.asCfgNode() = subscript
|
||||
)
|
||||
@@ -423,7 +422,7 @@ module Tornado {
|
||||
// be able to do something more structured for providing modeling of the members
|
||||
// of a container-object.
|
||||
exists(DataFlow::AttrRead files | files.accesses(instance(), "cookies") |
|
||||
this.asCfgNode().(Cfg::SubscriptNode).getObject() = files.asCfgNode()
|
||||
this.asCfgNode().(SubscriptNode).getObject() = files.asCfgNode()
|
||||
or
|
||||
this.(DataFlow::MethodCallNode).calls(files, "get")
|
||||
)
|
||||
@@ -480,20 +479,20 @@ module Tornado {
|
||||
// routing
|
||||
// ---------------------------------------------------------------------------
|
||||
/** Gets a sequence that defines a number of route rules */
|
||||
Cfg::SequenceNode routeSetupRuleList() {
|
||||
exists(Cfg::CallNode call |
|
||||
SequenceNode routeSetupRuleList() {
|
||||
exists(CallNode call |
|
||||
call = any(TornadoModule::Web::Application::ClassInstantiation c).asCfgNode()
|
||||
|
|
||||
result in [call.getArg(0), call.getArgByName("handlers")]
|
||||
)
|
||||
or
|
||||
exists(Cfg::CallNode call |
|
||||
exists(CallNode call |
|
||||
call.getFunction() = TornadoModule::Web::Application::add_handlers().asCfgNode()
|
||||
|
|
||||
result in [call.getArg(1), call.getArgByName("host_handlers")]
|
||||
)
|
||||
or
|
||||
result = routeSetupRuleList().getElement(_).(Cfg::TupleNode).getElement(1)
|
||||
result = routeSetupRuleList().getElement(_).(TupleNode).getElement(1)
|
||||
}
|
||||
|
||||
/** A tornado route setup. */
|
||||
@@ -516,12 +515,12 @@ module Tornado {
|
||||
|
||||
/** A route setup using a tuple. */
|
||||
private class TornadoTupleRouteSetup extends TornadoRouteSetup, DataFlow::CfgNode {
|
||||
override Cfg::TupleNode node;
|
||||
override TupleNode node;
|
||||
|
||||
TornadoTupleRouteSetup() {
|
||||
node = routeSetupRuleList().getElement(_) and
|
||||
count(node.getElement(_)) = 2 and
|
||||
not node.getElement(1) instanceof Cfg::SequenceNode
|
||||
not node.getElement(1) instanceof SequenceNode
|
||||
}
|
||||
|
||||
override DataFlow::Node getUrlPatternArg() { result.asCfgNode() = node.getElement(0) }
|
||||
|
||||
@@ -182,10 +182,7 @@ private module Twisted {
|
||||
DataFlow::CfgNode
|
||||
{
|
||||
TwistedResourceRenderMethodReturn() {
|
||||
exists(Return ret |
|
||||
ret.getScope() = any(TwistedResourceRenderMethod meth) and
|
||||
this.asCfgNode().getNode() = ret.getValue()
|
||||
)
|
||||
this.asCfgNode() = any(TwistedResourceRenderMethod meth).getAReturnValueFlowNode()
|
||||
}
|
||||
|
||||
override DataFlow::Node getBody() { result = this }
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
*/
|
||||
|
||||
private import python
|
||||
private import semmle.python.controlflow.internal.Cfg as Cfg
|
||||
private import semmle.python.dataflow.new.DataFlow
|
||||
private import semmle.python.dataflow.new.TaintTracking
|
||||
private import semmle.python.ApiGraphs
|
||||
@@ -222,9 +221,9 @@ module Werkzeug {
|
||||
DataFlow::Node value;
|
||||
|
||||
HeaderWriteSubscript() {
|
||||
exists(Cfg::SubscriptNode subscript |
|
||||
exists(SubscriptNode subscript |
|
||||
this.asCfgNode() = subscript and
|
||||
value.asCfgNode() = subscript.(Cfg::DefinitionNode).getValue() and
|
||||
value.asCfgNode() = subscript.(DefinitionNode).getValue() and
|
||||
name.asCfgNode() = subscript.getIndex() and
|
||||
subscript.getObject() = instance().asCfgNode()
|
||||
)
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
*/
|
||||
|
||||
private import python
|
||||
private import semmle.python.controlflow.internal.Cfg as Cfg
|
||||
private import semmle.python.dataflow.new.DataFlow
|
||||
private import semmle.python.Concepts
|
||||
private import semmle.python.ApiGraphs
|
||||
@@ -29,7 +28,7 @@ private module Yaml {
|
||||
* See https://pyyaml.org/wiki/PyYAMLDocumentation (you will have to scroll down).
|
||||
*/
|
||||
private class YamlLoadCall extends Decoding::Range, DataFlow::CallCfgNode {
|
||||
override Cfg::CallNode node;
|
||||
override CallNode node;
|
||||
string func_name;
|
||||
|
||||
YamlLoadCall() {
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
*/
|
||||
|
||||
private import python
|
||||
private import semmle.python.controlflow.internal.Cfg as Cfg
|
||||
private import semmle.python.dataflow.new.DataFlow
|
||||
private import semmle.python.dataflow.new.TaintTracking
|
||||
private import semmle.python.Concepts
|
||||
@@ -112,7 +111,7 @@ module Yarl {
|
||||
}
|
||||
|
||||
private predicate yarlUrlIsAbsoluteCall(
|
||||
DataFlow::GuardNode g, Cfg::ControlFlowNode node, boolean branch
|
||||
DataFlow::GuardNode g, ControlFlowNode node, boolean branch
|
||||
) {
|
||||
exists(ClassInstantiation instance, DataFlow::MethodCallNode call |
|
||||
call.calls(instance, "is_absolute") and
|
||||
|
||||
@@ -11,7 +11,6 @@ private import semmle.python.dataflow.new.internal.ImportResolution
|
||||
private import semmle.python.ApiGraphs
|
||||
private import semmle.python.filters.Tests
|
||||
private import semmle.python.Module
|
||||
private import semmle.python.controlflow.internal.Cfg as Cfg
|
||||
|
||||
// very much inspired by the draft at https://github.com/github/codeql/pull/5632
|
||||
module NotExposed {
|
||||
@@ -207,7 +206,7 @@ module NotExposed {
|
||||
string relevantName, Location loc
|
||||
) {
|
||||
loc = mod.getLocation() and
|
||||
exists(API::Node relevantClass, Cfg::ControlFlowNode value |
|
||||
exists(API::Node relevantClass, ControlFlowNode value |
|
||||
relevantClass = newOrExistingModeling(spec).getASubclass*() and
|
||||
ImportResolution::module_export(mod, relevantName, def) and
|
||||
value = relevantClass.getAValueReachableFromSource().asCfgNode() and
|
||||
|
||||
6
python/ql/lib/semmle/python/frameworks/lxml.model.yml
Normal file
6
python/ql/lib/semmle/python/frameworks/lxml.model.yml
Normal file
@@ -0,0 +1,6 @@
|
||||
extensions:
|
||||
- addsTo:
|
||||
pack: codeql/python-all
|
||||
extensible: summaryModel
|
||||
data:
|
||||
- ['lxml', 'Member[etree].Member[fromstringlist]', 'Argument[0,strings:].ListElement', 'ReturnValue', 'taint']
|
||||
6
python/ql/lib/semmle/python/frameworks/xml.model.yml
Normal file
6
python/ql/lib/semmle/python/frameworks/xml.model.yml
Normal file
@@ -0,0 +1,6 @@
|
||||
extensions:
|
||||
- addsTo:
|
||||
pack: codeql/python-all
|
||||
extensible: summaryModel
|
||||
data:
|
||||
- ['xml', 'Member[etree].Member[fromstringlist]', 'Argument[0,strings:].ListElement', 'ReturnValue', 'taint']
|
||||
@@ -77,7 +77,7 @@ module Stages {
|
||||
or
|
||||
exists(any(AstExtended::AstNode n).getParentNode())
|
||||
or
|
||||
exists(PyFlow::ControlFlowNode cfg, AstExtended::AstNode n | cfg.getNode() = n)
|
||||
exists(any(AstExtended::AstNode n).getAFlowNode())
|
||||
or
|
||||
exists(any(PyFlow::BasicBlock b).getImmediateDominator())
|
||||
or
|
||||
|
||||
@@ -56,9 +56,8 @@ abstract class CallableObjectInternal extends ObjectInternal {
|
||||
/** A Python function. */
|
||||
class PythonFunctionObjectInternal extends CallableObjectInternal, TPythonFunctionObject {
|
||||
override Function getScope() {
|
||||
exists(CallableExpr expr, ControlFlowNode exprCfg |
|
||||
exprCfg.getNode() = expr and
|
||||
this = TPythonFunctionObject(exprCfg) and
|
||||
exists(CallableExpr expr |
|
||||
this = TPythonFunctionObject(expr.getAFlowNode()) and
|
||||
result = expr.getInnerScope()
|
||||
)
|
||||
}
|
||||
@@ -81,12 +80,11 @@ class PythonFunctionObjectInternal extends CallableObjectInternal, TPythonFuncti
|
||||
|
||||
pragma[nomagic]
|
||||
override predicate callResult(PointsToContext callee, ObjectInternal obj, CfgOrigin origin) {
|
||||
exists(Function func, Return ret, ControlFlowNode rval, ControlFlowNode forigin |
|
||||
exists(Function func, ControlFlowNode rval, ControlFlowNode forigin |
|
||||
func = this.getScope() and
|
||||
callee.appliesToScope(func)
|
||||
|
|
||||
ret.getScope() = func and
|
||||
rval.getNode() = ret.getValue() and
|
||||
rval = func.getAReturnValueFlowNode() and
|
||||
PointsToInternal::pointsTo(rval, callee, obj, forigin) and
|
||||
origin = CfgOrigin::fromCfgNode(forigin)
|
||||
)
|
||||
@@ -162,11 +160,10 @@ class PythonFunctionObjectInternal extends CallableObjectInternal, TPythonFuncti
|
||||
}
|
||||
|
||||
private BasicBlock blockReturningNone(Function func) {
|
||||
exists(Return ret, ControlFlowNode ret_ |
|
||||
exists(Return ret |
|
||||
not exists(ret.getValue()) and
|
||||
ret.getScope() = func and
|
||||
ret_.getNode() = ret and
|
||||
result = ret_.getBasicBlock()
|
||||
result = ret.getAFlowNode().getBasicBlock()
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user